Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
1d9f78b3a4
commit
a66948df0c
|
@ -31,6 +31,30 @@
|
|||
"alternate": "ee/lib/{}.rb",
|
||||
"type": "test"
|
||||
},
|
||||
"app/assets/javascripts/*.js": {
|
||||
"alternate": "spec/frontend/{}_spec.js",
|
||||
"type": "source"
|
||||
},
|
||||
"app/assets/javascripts/*.vue": {
|
||||
"alternate": "spec/frontend/{}_spec.js",
|
||||
"type": "source"
|
||||
},
|
||||
"spec/frontend/*_spec.js": {
|
||||
"alternate": ["app/assets/javascripts/{}.vue", "app/assets/javascripts/{}.js"],
|
||||
"type": "test"
|
||||
},
|
||||
"ee/app/assets/javascripts/*.js": {
|
||||
"alternate": "ee/spec/frontend/{}_spec.js",
|
||||
"type": "source"
|
||||
},
|
||||
"ee/app/assets/javascripts/*.vue": {
|
||||
"alternate": "ee/spec/frontend/{}_spec.js",
|
||||
"type": "source"
|
||||
},
|
||||
"ee/spec/frontend/*_spec.js": {
|
||||
"alternate": ["ee/app/assets/javascripts/{}.vue", "ee/app/assets/javascripts/{}.js"],
|
||||
"type": "test"
|
||||
},
|
||||
"*.rb": {"dispatch": "bundle exec rubocop {file}"},
|
||||
"*_spec.rb": {"dispatch": "bundle exec rspec {file}"}
|
||||
}
|
||||
|
|
|
@ -5,9 +5,12 @@ import initSortDiscussions from './sort_discussions';
|
|||
import { store } from './stores';
|
||||
import initTimelineToggle from './timeline';
|
||||
|
||||
const el = document.getElementById('js-vue-notes');
|
||||
export default () => {
|
||||
const el = document.getElementById('js-vue-notes');
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (el) {
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
|
@ -59,4 +62,4 @@ if (el) {
|
|||
initDiscussionFilters(store);
|
||||
initSortDiscussions(store);
|
||||
initTimelineToggle(store);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -23,10 +23,10 @@ import { s__, __ } from '~/locale';
|
|||
// import DependencyRow from '~/packages/details/components/dependency_row.vue';
|
||||
// import InstallationCommands from '~/packages/details/components/installation_commands.vue';
|
||||
// import PackageFiles from '~/packages/details/components/package_files.vue';
|
||||
// import PackageHistory from '~/packages/details/components/package_history.vue';
|
||||
// import PackageListRow from '~/packages/shared/components/package_list_row.vue';
|
||||
import PackagesListLoader from '~/packages/shared/components/packages_list_loader.vue';
|
||||
import { packageTypeToTrackCategory } from '~/packages/shared/utils';
|
||||
import PackageHistory from '~/packages_and_registries/package_registry/components/details/package_history.vue';
|
||||
import {
|
||||
PACKAGE_TYPE_NUGET,
|
||||
PACKAGE_TYPE_COMPOSER,
|
||||
|
@ -60,7 +60,7 @@ export default {
|
|||
PackagesListLoader,
|
||||
// PackageListRow,
|
||||
// DependencyRow,
|
||||
// PackageHistory,
|
||||
PackageHistory,
|
||||
// AdditionalMetadata,
|
||||
// InstallationCommands,
|
||||
// PackageFiles,
|
||||
|
@ -124,10 +124,10 @@ export default {
|
|||
return this.packageEntity.packageFiles;
|
||||
},
|
||||
isLoading() {
|
||||
return this.$apollo.queries.package;
|
||||
return this.$apollo.queries.packageEntity.loading;
|
||||
},
|
||||
isValidPackage() {
|
||||
return Boolean(this.packageEntity?.name);
|
||||
return this.isLoading || Boolean(this.packageEntity?.name);
|
||||
},
|
||||
tracking() {
|
||||
return {
|
||||
|
@ -237,10 +237,10 @@ export default {
|
|||
|
||||
<gl-tabs>
|
||||
<gl-tab :title="__('Detail')">
|
||||
<div data-qa-selector="package_information_content">
|
||||
<!-- <package-history :package-entity="packageEntity" :project-name="projectName" />
|
||||
<div v-if="!isLoading" data-qa-selector="package_information_content">
|
||||
<package-history :package-entity="packageEntity" :project-name="projectName" />
|
||||
|
||||
<installation-commands
|
||||
<!-- <installation-commands
|
||||
:package-entity="packageEntity"
|
||||
:npm-path="npmPath"
|
||||
:npm-help-path="npmHelpPath"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { GlLink, GlSprintf } from '@gitlab/ui';
|
||||
import { first } from 'lodash';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import { truncateSha } from '~/lib/utils/text_utility';
|
||||
import { s__, n__ } from '~/locale';
|
||||
import { HISTORY_PIPELINES_LIMIT } from '~/packages/details/constants';
|
||||
|
@ -45,7 +46,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
pipelines() {
|
||||
return this.packageEntity.pipelines || [];
|
||||
return this.packageEntity?.pipelines?.nodes || [];
|
||||
},
|
||||
firstPipeline() {
|
||||
return first(this.pipelines);
|
||||
|
@ -71,6 +72,9 @@ export default {
|
|||
truncate(value) {
|
||||
return truncateSha(value);
|
||||
},
|
||||
convertToBaseId(value) {
|
||||
return getIdFromGraphQLId(value);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -88,7 +92,7 @@ export default {
|
|||
<strong>{{ packageEntity.version }}</strong>
|
||||
</template>
|
||||
<template #datetime>
|
||||
<time-ago-tooltip :time="packageEntity.created_at" />
|
||||
<time-ago-tooltip :time="packageEntity.createdAt" />
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</history-item>
|
||||
|
@ -98,9 +102,7 @@ export default {
|
|||
<history-item icon="commit" data-testid="first-pipeline-commit">
|
||||
<gl-sprintf :message="$options.i18n.createdByCommitText">
|
||||
<template #link>
|
||||
<gl-link :href="firstPipeline.project.commit_url"
|
||||
>#{{ truncate(firstPipeline.sha) }}</gl-link
|
||||
>
|
||||
<gl-link :href="firstPipeline.commitPath">#{{ truncate(firstPipeline.sha) }}</gl-link>
|
||||
</template>
|
||||
<template #branch>
|
||||
<strong>{{ firstPipeline.ref }}</strong>
|
||||
|
@ -110,10 +112,10 @@ export default {
|
|||
<history-item icon="pipeline" data-testid="first-pipeline-pipeline">
|
||||
<gl-sprintf :message="$options.i18n.createdByPipelineText">
|
||||
<template #link>
|
||||
<gl-link :href="firstPipeline.project.pipeline_url">#{{ firstPipeline.id }}</gl-link>
|
||||
<gl-link :href="firstPipeline.path">#{{ convertToBaseId(firstPipeline.id) }}</gl-link>
|
||||
</template>
|
||||
<template #datetime>
|
||||
<time-ago-tooltip :time="firstPipeline.created_at" />
|
||||
<time-ago-tooltip :time="firstPipeline.createdAt" />
|
||||
</template>
|
||||
<template #author>{{ firstPipeline.user.name }}</template>
|
||||
</gl-sprintf>
|
||||
|
@ -127,7 +129,7 @@ export default {
|
|||
<strong>{{ projectName }}</strong>
|
||||
</template>
|
||||
<template #datetime>
|
||||
<time-ago-tooltip :time="packageEntity.created_at" />
|
||||
<time-ago-tooltip :time="packageEntity.createdAt" />
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</history-item>
|
||||
|
@ -149,16 +151,16 @@ export default {
|
|||
>
|
||||
<gl-sprintf :message="$options.i18n.combinedUpdateText">
|
||||
<template #link>
|
||||
<gl-link :href="pipeline.project.commit_url">#{{ truncate(pipeline.sha) }}</gl-link>
|
||||
<gl-link :href="pipeline.commitPath">#{{ truncate(pipeline.sha) }}</gl-link>
|
||||
</template>
|
||||
<template #branch>
|
||||
<strong>{{ pipeline.ref }}</strong>
|
||||
</template>
|
||||
<template #pipeline>
|
||||
<gl-link :href="pipeline.project.pipeline_url">#{{ pipeline.id }}</gl-link>
|
||||
<gl-link :href="pipeline.path">#{{ convertToBaseId(pipeline.id) }}</gl-link>
|
||||
</template>
|
||||
<template #datetime>
|
||||
<time-ago-tooltip :time="pipeline.created_at" />
|
||||
<time-ago-tooltip :time="pipeline.createdAt" />
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</history-item>
|
||||
|
|
|
@ -15,13 +15,22 @@ query getPackageDetails($id: ID!) {
|
|||
}
|
||||
pipelines(first: 3) {
|
||||
nodes {
|
||||
ref
|
||||
id
|
||||
sha
|
||||
createdAt
|
||||
commitPath
|
||||
path
|
||||
user {
|
||||
name
|
||||
}
|
||||
project {
|
||||
name
|
||||
webUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
packageFiles(first: 1000) {
|
||||
packageFiles(first: 100) {
|
||||
nodes {
|
||||
id
|
||||
fileMd5
|
||||
|
|
|
@ -3,10 +3,10 @@ import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
|
|||
import initIssuableSidebar from '~/init_issuable_sidebar';
|
||||
import { IssuableType } from '~/issuable_show/constants';
|
||||
import Issue from '~/issue';
|
||||
import '~/notes/index';
|
||||
import initIncidentApp from '~/issue_show/incident';
|
||||
import { initIssuableApp, initIssueHeaderActions } from '~/issue_show/issue';
|
||||
import { parseIssuableData } from '~/issue_show/utils/parse_data';
|
||||
import initNotesApp from '~/notes/index';
|
||||
import { store } from '~/notes/stores';
|
||||
import initRelatedMergeRequestsApp from '~/related_merge_requests';
|
||||
import initSentryErrorStackTraceApp from '~/sentry_error_stack_trace';
|
||||
|
@ -14,6 +14,8 @@ import initIssuableHeaderWarning from '~/vue_shared/components/issuable/init_iss
|
|||
import ZenMode from '~/zen_mode';
|
||||
|
||||
export default function initShowIssue() {
|
||||
initNotesApp();
|
||||
|
||||
const initialDataEl = document.getElementById('js-issuable-app');
|
||||
const { issueType, ...issuableData } = parseIssuableData(initialDataEl);
|
||||
|
||||
|
|
|
@ -22,6 +22,13 @@ class JiraConnect::SubscriptionsController < JiraConnect::ApplicationController
|
|||
|
||||
def index
|
||||
@subscriptions = current_jira_installation.subscriptions.preload_namespace_route
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json do
|
||||
render json: JiraConnect::AppDataSerializer.new(@subscriptions, !!current_user).as_json
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
|
|
|
@ -8,6 +8,7 @@ class Projects::ServicesController < Projects::ApplicationController
|
|||
before_action :authorize_admin_project!
|
||||
before_action :ensure_service_enabled
|
||||
before_action :integration
|
||||
before_action :default_integration, only: [:edit, :update]
|
||||
before_action :web_hook_logs, only: [:edit, :update]
|
||||
before_action :set_deprecation_notice_for_prometheus_integration, only: [:edit, :update]
|
||||
before_action :redirect_deprecated_prometheus_integration, only: [:update]
|
||||
|
@ -19,14 +20,22 @@ class Projects::ServicesController < Projects::ApplicationController
|
|||
feature_category :integrations
|
||||
|
||||
def edit
|
||||
@default_integration = Integration.default_integration(service.type, project)
|
||||
end
|
||||
|
||||
def update
|
||||
@integration.attributes = integration_params[:integration]
|
||||
@integration.inherit_from_id = nil if integration_params[:integration][:inherit_from_id].blank?
|
||||
attributes = integration_params[:integration]
|
||||
|
||||
if use_inherited_settings?(attributes)
|
||||
@integration.inherit_from_id = default_integration.id
|
||||
|
||||
if saved = @integration.save(context: :manual_change)
|
||||
BulkUpdateIntegrationService.new(default_integration, [@integration]).execute
|
||||
end
|
||||
else
|
||||
attributes[:inherit_from_id] = nil
|
||||
@integration.attributes = attributes
|
||||
saved = @integration.save(context: :manual_change)
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
|
@ -88,6 +97,10 @@ class Projects::ServicesController < Projects::ApplicationController
|
|||
end
|
||||
alias_method :service, :integration
|
||||
|
||||
def default_integration
|
||||
@default_integration ||= Integration.default_integration(integration.type, project)
|
||||
end
|
||||
|
||||
def web_hook_logs
|
||||
return unless integration.service_hook.present?
|
||||
|
||||
|
@ -115,4 +128,8 @@ class Projects::ServicesController < Projects::ApplicationController
|
|||
message = s_('PrometheusService|You can now manage your Prometheus settings on the %{operations_link_start}Operations%{operations_link_end} page. Fields on this page have been deprecated.') % { operations_link_start: operations_link_start, operations_link_end: "</a>" }
|
||||
flash.now[:alert] = message.html_safe
|
||||
end
|
||||
|
||||
def use_inherited_settings?(attributes)
|
||||
default_integration && attributes[:inherit_from_id] == default_integration.id.to_s
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,6 +19,11 @@ module Types
|
|||
argument :release_tag, GraphQL::Types::String,
|
||||
required: false,
|
||||
description: 'Filter by release tag.'
|
||||
|
||||
argument :types, [Types::IssueTypeEnum],
|
||||
as: :issue_types,
|
||||
description: 'Filter by the given issue types.',
|
||||
required: false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -31,4 +31,9 @@ module TimeZoneHelper
|
|||
}.slice(*attrs)
|
||||
end
|
||||
end
|
||||
|
||||
def local_time(timezone)
|
||||
time_zone_instance = ActiveSupport::TimeZone.new(timezone) || Time.zone
|
||||
time_zone_instance.now.strftime("%-l:%M %p")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class JiraConnect::AppDataSerializer
|
||||
include Gitlab::Routing
|
||||
include ::API::Helpers::RelatedResourcesHelpers
|
||||
|
||||
def initialize(subscriptions, signed_in)
|
||||
@subscriptions = subscriptions
|
||||
@signed_in = signed_in
|
||||
end
|
||||
|
||||
def as_json
|
||||
skip_groups = @subscriptions.map(&:namespace_id)
|
||||
|
||||
{
|
||||
groups_path: api_v4_groups_path(params: { min_access_level: Gitlab::Access::MAINTAINER, skip_groups: skip_groups }),
|
||||
subscriptions: JiraConnect::SubscriptionEntity.represent(@subscriptions).as_json,
|
||||
subscriptions_path: jira_connect_subscriptions_path,
|
||||
login_path: signed_in? ? nil : jira_connect_users_path
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def signed_in?
|
||||
!!@signed_in
|
||||
end
|
||||
end
|
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class JiraConnect::GroupEntity < Grape::Entity
|
||||
expose :name
|
||||
expose :avatar_url
|
||||
expose :full_name
|
||||
expose :description
|
||||
end
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class JiraConnect::SubscriptionEntity < Grape::Entity
|
||||
include Gitlab::Routing
|
||||
|
||||
expose :created_at
|
||||
expose :unlink_path do |subscription|
|
||||
jira_connect_subscription_path(subscription)
|
||||
end
|
||||
expose :namespace, with: JiraConnect::GroupEntity, as: :group
|
||||
end
|
|
@ -42,7 +42,7 @@ module Boards
|
|||
end
|
||||
|
||||
def set_issue_types
|
||||
params[:issue_types] = Issue::TYPES_FOR_LIST
|
||||
params[:issue_types] ||= Issue::TYPES_FOR_LIST
|
||||
end
|
||||
|
||||
def item_model
|
||||
|
|
|
@ -9,10 +9,10 @@ class BulkUpdateIntegrationService
|
|||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def execute
|
||||
Integration.transaction do
|
||||
Integration.where(id: batch.select(:id)).update_all(integration_hash)
|
||||
Integration.where(id: batch_ids).update_all(integration_hash)
|
||||
|
||||
if integration.data_fields_present?
|
||||
integration.data_fields.class.where(service_id: batch.select(:id)).update_all(data_fields_hash)
|
||||
integration.data_fields.class.where(service_id: batch_ids).update_all(data_fields_hash)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -29,4 +29,13 @@ class BulkUpdateIntegrationService
|
|||
def data_fields_hash
|
||||
integration.to_data_fields_hash
|
||||
end
|
||||
|
||||
def batch_ids
|
||||
@batch_ids ||=
|
||||
if batch.is_a?(ActiveRecord::Relation)
|
||||
batch.select(:id)
|
||||
else
|
||||
batch.map(&:id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
= sprite_icon('location', css_class: 'fgray')
|
||||
%span{ itemprop: 'addressLocality' }
|
||||
= @user.location
|
||||
.profile-link-holder.middle-dot-divider-sm.d-block.d-sm-inline.mb-1.mb-sm-0
|
||||
= sprite_icon('clock', css_class: 'fgray')
|
||||
%span
|
||||
= local_time(@user.timezone)
|
||||
- unless work_information(@user).blank?
|
||||
.profile-link-holder.middle-dot-divider-sm.d-block.d-sm-inline
|
||||
= sprite_icon('work', css_class: 'fgray')
|
||||
|
|
|
@ -11,13 +11,12 @@ class ScheduleDeleteOrphanedDeployments < ActiveRecord::Migration[6.1]
|
|||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
queue_background_migration_jobs_by_range_at_intervals(
|
||||
define_batchable_model('deployments'),
|
||||
MIGRATION,
|
||||
DELAY_INTERVAL,
|
||||
batch_size: BATCH_SIZE,
|
||||
track_jobs: true
|
||||
)
|
||||
# no-op.
|
||||
# This background migration is rescheduled in 20210722010101_cleanup_delete_orphaned_deployments_background_migration.rb
|
||||
# with a smaller batch size, because the initial attempt caused
|
||||
# 80 failures out of 1639 batches (faiulre rate is 4.88%) due to statement timeouts,
|
||||
# that takes approx. 1 hour to perform a cleanup/sync migration.
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/335071#note_618380503 for more information.
|
||||
end
|
||||
|
||||
def down
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RescheduleDeleteOrphanedDeployments < ActiveRecord::Migration[6.1]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
MIGRATION = 'DeleteOrphanedDeployments'
|
||||
BATCH_SIZE = 10_000
|
||||
DELAY_INTERVAL = 2.minutes
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
Gitlab::BackgroundMigration.steal(MIGRATION)
|
||||
Gitlab::Database::BackgroundMigrationJob.for_migration_class(MIGRATION).delete_all
|
||||
|
||||
queue_background_migration_jobs_by_range_at_intervals(
|
||||
define_batchable_model('deployments'),
|
||||
MIGRATION,
|
||||
DELAY_INTERVAL,
|
||||
batch_size: BATCH_SIZE,
|
||||
track_jobs: true
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
# no-op
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
bbd39849499d16f92a5129506a87a6b253f209200bcb3a63c2432862c4b78aae
|
|
@ -44,15 +44,17 @@ gitlab-ctl restart
|
|||
|
||||
## Changing time zone per user
|
||||
|
||||
To allow users to change the time zone in their profile, the feature flag `user_time_settings` should be enabled:
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/57654) in GitLab 11.11, disabled by default behind `user_time_settings` [feature flag](feature_flags.md).
|
||||
> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/29669) in GitLab 13.9.
|
||||
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/29669) in GitLab 14.1.
|
||||
|
||||
1. [Start a Rails console session](operations/rails_console.md).
|
||||
1. Enable the feature flag:
|
||||
This setting controls the local time displayed on a user's profile.
|
||||
See [&280](https://gitlab.com/groups/gitlab-org/-/epics/280) for other planned features.
|
||||
|
||||
```ruby
|
||||
Feature.enable(:user_time_settings)
|
||||
```
|
||||
NOTE:
|
||||
If a user has not set their time zone, it defaults to the time zone [configured at the instance level](#changing-your-time-zone). On GitLab.com, the default time zone is UTC.
|
||||
|
||||
1. You should now be able to see the timezone dropdown in the users' **Settings > Profile** page.
|
||||
1. Navigate to [your user settings](../user/profile/index.md#access-your-user-settings).
|
||||
1. Select your time zone from the dropdown.
|
||||
|
||||
![User Time Zone Settings](img/time_zone_settings.png)
|
||||
![User Time Zone Settings](img/time_zone_settings.png)
|
||||
|
|
|
@ -16584,6 +16584,7 @@ Field that are available while modifying the custom mapping attributes for an HT
|
|||
| <a id="boardissueinputnot"></a>`not` | [`NegatedBoardIssueInput`](#negatedboardissueinput) | List of negated arguments. |
|
||||
| <a id="boardissueinputreleasetag"></a>`releaseTag` | [`String`](#string) | Filter by release tag. |
|
||||
| <a id="boardissueinputsearch"></a>`search` | [`String`](#string) | Search query for issue title or description. |
|
||||
| <a id="boardissueinputtypes"></a>`types` | [`[IssueType!]`](#issuetype) | Filter by the given issue types. |
|
||||
| <a id="boardissueinputweight"></a>`weight` | [`String`](#string) | Filter by weight. |
|
||||
| <a id="boardissueinputweightwildcardid"></a>`weightWildcardId` | [`WeightWildcardId`](#weightwildcardid) | Filter by weight ID wildcard. Incompatible with weight. |
|
||||
|
||||
|
@ -16736,6 +16737,7 @@ Represents an escalation rule.
|
|||
| <a id="negatedboardissueinputmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter by milestone title. |
|
||||
| <a id="negatedboardissueinputmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. |
|
||||
| <a id="negatedboardissueinputreleasetag"></a>`releaseTag` | [`String`](#string) | Filter by release tag. |
|
||||
| <a id="negatedboardissueinputtypes"></a>`types` | [`[IssueType!]`](#issuetype) | Filter by the given issue types. |
|
||||
| <a id="negatedboardissueinputweight"></a>`weight` | [`String`](#string) | Filter by weight. |
|
||||
|
||||
### `NegatedEpicBoardIssueInput`
|
||||
|
|
|
@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
type: howto
|
||||
---
|
||||
|
||||
# Cloud deployment
|
||||
# Cloud deployment **(FREE)**
|
||||
|
||||
Interacting with a major cloud provider may have become a much needed task that's
|
||||
part of your delivery process. With GitLab you can
|
||||
|
|
|
@ -4,7 +4,7 @@ group: Release
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Deployment safety
|
||||
# Deployment safety **(FREE)**
|
||||
|
||||
Deployment jobs can be more sensitive than other jobs in a pipeline,
|
||||
and might need to be treated with extra care. GitLab has several features
|
||||
|
|
|
@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
type: concepts, howto
|
||||
---
|
||||
|
||||
# Incremental Rollouts with GitLab CI/CD
|
||||
# Incremental rollouts with GitLab CI/CD **(FREE)**
|
||||
|
||||
When rolling out changes to your application, it is possible to release production changes
|
||||
to only a portion of your Kubernetes pods as a risk mitigation strategy. By releasing
|
||||
|
@ -37,8 +37,6 @@ use as examples to build your own:
|
|||
|
||||
## Manual Rollouts
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/5415) in GitLab 10.8.
|
||||
|
||||
It is possible to configure GitLab to do incremental rollouts manually through `.gitlab-ci.yml`. Manual configuration
|
||||
allows more control over the this feature. The steps in an incremental rollout depend on the
|
||||
number of pods that are defined for the deployment, which are configured when the Kubernetes
|
||||
|
@ -77,8 +75,6 @@ available, demonstrating manually triggered incremental rollouts.
|
|||
|
||||
## Timed Rollouts
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7545) in GitLab 11.4.
|
||||
|
||||
Timed rollouts behave in the same way as manual rollouts, except that each job is defined with a
|
||||
delay in minutes before it deploys. Clicking the job reveals the countdown.
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
type: tutorial
|
||||
---
|
||||
|
||||
# Authenticating and Reading Secrets With HashiCorp Vault
|
||||
# Authenticating and reading secrets with HashiCorp Vault **(PREMIUM)**
|
||||
|
||||
This tutorial demonstrates how to authenticate, configure, and read secrets with HashiCorp's Vault from GitLab CI/CD.
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
type: tutorial
|
||||
---
|
||||
|
||||
# Running Composer and npm scripts with deployment via SCP in GitLab CI/CD
|
||||
# Running Composer and npm scripts with deployment via SCP in GitLab CI/CD **(FREE)**
|
||||
|
||||
This guide covers the building of dependencies of a PHP project while compiling assets via an npm script using [GitLab CI/CD](../../README.md).
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
type: tutorial
|
||||
---
|
||||
|
||||
# Using Dpl as deployment tool
|
||||
# Using Dpl as a deployment tool **(FREE)**
|
||||
|
||||
[Dpl](https://github.com/travis-ci/dpl) (pronounced like the letters D-P-L) is a deploy tool made for
|
||||
continuous deployment that's developed and used by Travis CI, but can also be
|
||||
|
@ -105,18 +105,18 @@ production:
|
|||
|
||||
We created two deploy jobs that are executed on different events:
|
||||
|
||||
1. `staging` is executed for all commits that were pushed to `master` branch,
|
||||
1. `production` is executed for all pushed tags.
|
||||
- `staging`: Executed for all commits pushed to the `master` branch
|
||||
- `production`: Executed for all pushed tags
|
||||
|
||||
We also use two secure variables:
|
||||
|
||||
1. `HEROKU_STAGING_API_KEY` - Heroku API key used to deploy staging app,
|
||||
1. `HEROKU_PRODUCTION_API_KEY` - Heroku API key used to deploy production app.
|
||||
- `HEROKU_STAGING_API_KEY`: Heroku API key used to deploy staging app
|
||||
- `HEROKU_PRODUCTION_API_KEY`: Heroku API key used to deploy production app
|
||||
|
||||
## Storing API keys
|
||||
|
||||
To add secure variables, navigate to your project's
|
||||
**Settings > CI/CD > Variables**. The variables that are defined
|
||||
**Settings > CI/CD > Variables**. The variables defined
|
||||
in the project settings are sent along with the build script to the runner.
|
||||
The secure variables are stored out of the repository. Never store secrets in
|
||||
your project's `.gitlab-ci.yml`. It is also important that the secret's value
|
||||
|
@ -125,7 +125,7 @@ is hidden in the job log.
|
|||
You access added variable by prefixing it's name with `$` (on non-Windows runners)
|
||||
or `%` (for Windows Batch runners):
|
||||
|
||||
1. `$VARIABLE` - use it for non-Windows runners
|
||||
1. `%VARIABLE%` - use it for Windows Batch runners
|
||||
- `$VARIABLE`: Use for non-Windows runners
|
||||
- `%VARIABLE%`: Use for Windows Batch runners
|
||||
|
||||
Read more about the [CI/CD variables](../../variables/index.md).
|
||||
|
|
|
@ -5,19 +5,14 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
type: reference
|
||||
---
|
||||
|
||||
# Review Apps
|
||||
# Review Apps **(FREE)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/21971) in GitLab 8.12. Further additions were made in GitLab 8.13 and 8.14.
|
||||
> - Inspired by [Heroku's Review Apps](https://devcenter.heroku.com/articles/github-integration-review-apps), which itself was inspired by [Fourchette](https://github.com/rainforestapp/fourchette).
|
||||
|
||||
Review Apps is a collaboration tool that takes the hard work out of providing an environment to showcase product changes.
|
||||
Review Apps is a collaboration tool that assists with providing an environment to showcase product changes.
|
||||
|
||||
NOTE:
|
||||
If you have a Kubernetes cluster, you can automate this feature in your applications
|
||||
by using [Auto DevOps](../../topics/autodevops/index.md).
|
||||
|
||||
## Introduction
|
||||
|
||||
Review Apps:
|
||||
|
||||
- Provide an automatic live preview of changes made in a feature branch by spinning up a dynamic environment for your merge requests.
|
||||
|
@ -27,7 +22,7 @@ Review Apps:
|
|||
|
||||
![Review Apps Workflow](img/continuous-delivery-review-apps.svg)
|
||||
|
||||
In the above example:
|
||||
In the previous example:
|
||||
|
||||
- A Review App is built every time a commit is pushed to `topic branch`.
|
||||
- The reviewer fails two reviews before passing the third review.
|
||||
|
@ -107,8 +102,6 @@ Other examples of Review Apps:
|
|||
|
||||
## Route Maps
|
||||
|
||||
> Introduced in GitLab 8.17. In GitLab 11.5, the file links are available in the merge request widget.
|
||||
|
||||
Route Maps allows you to go directly from source files
|
||||
to public pages on the [environment](../environments/index.md) defined for
|
||||
Review Apps.
|
||||
|
|
|
@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
type: concepts, howto
|
||||
---
|
||||
|
||||
# Using external secrets in CI
|
||||
# Using external secrets in CI **(FREE)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218746) in GitLab 13.4 and GitLab Runner 13.4.
|
||||
> - `file` setting [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/250695) in GitLab 14.1 and GitLab Runner 14.1.
|
||||
|
|
|
@ -34,9 +34,14 @@ any of the following Service Ping files:
|
|||
|
||||
#### The merge request **author** should
|
||||
|
||||
- Decide whether a Product Intelligence review is needed.
|
||||
- Decide whether a Product Intelligence review is needed. You can skip the Product Intelligence
|
||||
review and remove the labels if the changes are not related to the Product Intelligence domain and
|
||||
are regular backend changes.
|
||||
- If a Product Intelligence review is needed, add the labels
|
||||
`~product intelligence` and `~product intelligence::review pending`.
|
||||
- For merge requests authored by Product Intelligence team members:
|
||||
- Assign both the `~backend` and `~product intelligence` reviews to another Product Intelligence team member.
|
||||
- Assign the maintainer review to someone outside of the Product Intelligence group.
|
||||
- Assign an
|
||||
[engineer](https://gitlab.com/groups/gitlab-org/growth/product-intelligence/engineers/-/group_members?with_inherited_permissions=exclude) from the Product Intelligence team for a review.
|
||||
- Set the correct attributes in the metric's YAML definition:
|
||||
|
|
|
@ -10,25 +10,24 @@ type: reference
|
|||
GitLab Inc. periodically collects information about your instance in order
|
||||
to perform various actions.
|
||||
|
||||
All statistics are opt-out. To enable or disable them:
|
||||
All usage statistics are [opt-out](#enable-or-disable-usage-statistics).
|
||||
|
||||
1. On the top bar, select **Menu >** **{admin}** **Admin**.
|
||||
1. In the left sidebar, select **Settings > Metrics and profiling**, and expand **Usage statistics**.
|
||||
1. Enable or disable **Version check** and **Service ping**.
|
||||
1. Select **Save changes**.
|
||||
## Service Ping **(FREE SELF)**
|
||||
|
||||
## Network configuration
|
||||
Service Ping is a process that collects and sends a weekly payload to GitLab Inc.
|
||||
For more information, see the [Service Ping guide](../../../development/service_ping/index.md).
|
||||
|
||||
Allow network traffic from your GitLab instance to IP address `104.196.17.203:443`, to send
|
||||
usage statistics to GitLab Inc.
|
||||
### Instance-level analytics availability
|
||||
|
||||
If your GitLab instance is behind a proxy, set the appropriate [proxy configuration variables](https://docs.gitlab.com/omnibus/settings/environment-variables.html).
|
||||
When Service Ping is enabled, GitLab gathers data from other instances and
|
||||
enables certain [instance-level analytics features](../analytics/index.md)
|
||||
that are dependent on Service Ping.
|
||||
|
||||
## Version Check **(FREE SELF)**
|
||||
## Version check **(FREE SELF)**
|
||||
|
||||
If enabled, version check informs you if a new version is available and the
|
||||
importance of it through a status. This is shown on the help page (i.e. `/help`)
|
||||
for all signed in users, and on the admin pages. The statuses are:
|
||||
importance of it through a status. The status displays on the help pages (`/help`)
|
||||
for all signed-in users, and on the Admin Area pages. The statuses are:
|
||||
|
||||
- Green: You are running the latest version of GitLab.
|
||||
- Orange: An updated version of GitLab is available.
|
||||
|
@ -44,17 +43,12 @@ This information is used, among other things, to identify to which versions
|
|||
patches must be backported, making sure active GitLab instances remain
|
||||
secure.
|
||||
|
||||
If you disable version check, this information isn't collected. To enable or disable it:
|
||||
|
||||
1. On the top bar, select **Menu >** **{admin}** **Admin**.
|
||||
1. In the left sidebar, select **Settings > Metrics and profiling**, and expand **Usage statistics**.
|
||||
1. Enable or disable **Version check**.
|
||||
1. Select **Save changes**.
|
||||
If you [disable version check](#enable-or-disable-usage-statistics), this information isn't collected.
|
||||
|
||||
### Request flow example
|
||||
|
||||
The following example shows a basic request/response flow between the self-managed GitLab instance
|
||||
and the GitLab Version Application:
|
||||
The following example shows a basic request/response flow between a
|
||||
self-managed GitLab instance and the GitLab Version Application:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
|
@ -67,14 +61,22 @@ sequenceDiagram
|
|||
Version Application->>GitLab instance: Response (PNG/SVG)
|
||||
```
|
||||
|
||||
## Service Ping **(FREE SELF)**
|
||||
## Configure your network
|
||||
|
||||
See [Service Ping guide](../../../development/service_ping/index.md).
|
||||
To send usage statistics to GitLab Inc., you must allow network traffic from your
|
||||
GitLab instance to the IP address `104.196.17.203:443`.
|
||||
|
||||
## Instance-level analytics availability
|
||||
If your GitLab instance is behind a proxy, set the appropriate
|
||||
[proxy configuration variables](https://docs.gitlab.com/omnibus/settings/environment-variables.html).
|
||||
|
||||
After Service Ping is enabled, GitLab gathers data from other instances and
|
||||
enables certain [instance-level analytics features](../analytics/index.md) that are dependent on Service Ping.
|
||||
## Enable or disable usage statistics
|
||||
|
||||
To enable or disable Service Ping and version check:
|
||||
|
||||
1. On the top bar, select **Menu >** **{admin}** **Admin**.
|
||||
1. In the left sidebar, select **Settings > Metrics and profiling**, and expand **Usage statistics**.
|
||||
1. Select or clear the **Version check** and **Service ping** checkboxes.
|
||||
1. Select **Save changes**.
|
||||
|
||||
<!-- ## Troubleshooting
|
||||
|
||||
|
|
|
@ -589,30 +589,6 @@ by the application as correctly authenticated.
|
|||
|
||||
Authentication supports single form logins, multi-step login forms, and authenticating to URLs outside of the configured target URL.
|
||||
|
||||
Variables that are related to authenticated scans are:
|
||||
|
||||
```yaml
|
||||
include:
|
||||
- template: DAST.gitlab-ci.yml
|
||||
|
||||
dast:
|
||||
variables:
|
||||
DAST_WEBSITE: "https://example.com"
|
||||
DAST_AUTH_URL: "https://login.example.com/"
|
||||
DAST_BROWSER_PATH_TO_LOGIN_FORM: "css:.navigation-menu,css:.login-menu-item" # optional list of selectors that should be clicked on prior to attempting to input username/password into the sign-in HTML form
|
||||
DAST_USERNAME: "admin"
|
||||
DAST_PASSWORD: "P@55w0rd!"
|
||||
DAST_USERNAME_FIELD: "name:username" # a selector describing the element containing the username field at the sign-in HTML form
|
||||
DAST_PASSWORD_FIELD: "id:password" # a selector describing the element containing the password field at the sign-in HTML form
|
||||
DAST_FIRST_SUBMIT_FIELD: "css:button[type='user-submit']" # optional, the selector of the element that when clicked will submit the username form of a multi-page login process
|
||||
DAST_SUBMIT_FIELD: "css:button[type='submit']" # the selector of the element that when clicked will submit the login form or the password form of a multi-page login process
|
||||
DAST_EXCLUDE_URLS: "http://example.com/sign-out" # optional, URLs to skip during the authenticated scan; comma-separated, no spaces in between
|
||||
DAST_AUTH_VERIFICATION_URL: "http://example.com/loggedin_page" # optional, used to verify authentication is successful by expecting this URL once the login form has been submitted
|
||||
DAST_AUTH_VERIFICATION_SELECTOR: "css:.user-profile" # optional, used to verify authentication is successful by expecting a selector to be present on the page once the login form has been submitted
|
||||
DAST_AUTH_VERIFICATION_LOGIN_FORM: "true" # optional, used to verify authentication is successful by ensuring there are no login forms on the page once the login form has been submitted
|
||||
DAST_AUTH_REPORT: "true" # optionally output an authentication debug report
|
||||
```
|
||||
|
||||
WARNING:
|
||||
**NEVER** run an authenticated scan against a production server. When an authenticated
|
||||
scan is run, it may perform *any* function that the authenticated user can. This
|
||||
|
@ -768,55 +744,56 @@ dast:
|
|||
|
||||
### Available CI/CD variables
|
||||
|
||||
You can use CI/CD variables to customize DAST.
|
||||
These CI/CD variables are specific to DAST. They can be used to customize the behavior of DAST to your requirements.
|
||||
|
||||
| CI/CD variable | Type | Description |
|
||||
|:------------------------------------------------|:--------------|:-------------------------------|
|
||||
| `SECURE_ANALYZERS_PREFIX` | URL | Set the Docker registry base address from which to download the analyzer. |
|
||||
| `DAST_WEBSITE` <sup>1</sup> | URL | The URL of the website to scan. `DAST_API_OPENAPI` must be specified if this is omitted. |
|
||||
| `DAST_API_OPENAPI` | URL or string | The API specification to import. The specification can be hosted at a URL, or the name of a file present in the `/zap/wrk` directory. `DAST_WEBSITE` must be specified if this is omitted. |
|
||||
| `DAST_API_SPECIFICATION` <sup>1</sup> | URL or string | [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/290241) in GitLab 13.12 and replaced by `DAST_API_OPENAPI`. To be removed in GitLab 15.0. The API specification to import. The specification can be hosted at a URL, or the name of a file present in the `/zap/wrk` directory. `DAST_WEBSITE` must be specified if this is omitted. |
|
||||
| `DAST_SPIDER_START_AT_HOST` | boolean | Set to `false` to prevent DAST from resetting the target to its host before scanning. When `true`, non-host targets `http://test.site/some_path` is reset to `http://test.site` before scan. Default: `true`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/258805) in GitLab 13.6. |
|
||||
| `DAST_AUTH_URL` <sup>1</sup> | URL | The URL of the page containing the sign-in HTML form on the target website. `DAST_USERNAME` and `DAST_PASSWORD` are submitted with the login form to create an authenticated scan. Not supported for API scans. |
|
||||
| `DAST_BROWSER_PATH_TO_LOGIN_FORM` <sup>1</sup> | selector | Comma-separated list of selectors that will be clicked on prior to attempting to enter `DAST_USERNAME` and `DAST_PASSWORD` into the login form. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/326633) in GitLab 14.1. |
|
||||
| `DAST_USERNAME` <sup>1</sup> | string | The username to authenticate to in the website. |
|
||||
| `DAST_PASSWORD` <sup>1</sup> | string | The password to authenticate to in the website. |
|
||||
| `DAST_USERNAME_FIELD` <sup>1</sup> | string | The name of username field at the sign-in HTML form. |
|
||||
| `DAST_PASSWORD_FIELD` <sup>1</sup> | string | The name of password field at the sign-in HTML form. |
|
||||
| `DAST_SKIP_TARGET_CHECK` | boolean | Set to `true` to prevent DAST from checking that the target is available before scanning. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229067) in GitLab 13.8. |
|
||||
| `DAST_MASK_HTTP_HEADERS` | string | Comma-separated list of request and response headers to be masked (GitLab 13.1). Must contain **all** headers to be masked. Refer to [list of headers that are masked by default](#hide-sensitive-information). |
|
||||
| `DAST_EXCLUDE_URLS` <sup>1</sup> | URLs | The URLs to skip during the authenticated scan; comma-separated. Regular expression syntax can be used to match multiple URLs. For example, `.*` matches an arbitrary character sequence. Not supported for API scans. |
|
||||
| `DAST_FULL_SCAN_ENABLED` <sup>1</sup> | boolean | Set to `true` to run a [ZAP Full Scan](https://www.zaproxy.org/docs/docker/full-scan/) instead of a [ZAP Baseline Scan](https://www.zaproxy.org/docs/docker/baseline-scan/). Default: `false` |
|
||||
| `DAST_FULL_SCAN_DOMAIN_VALIDATION_REQUIRED` | boolean | **{warning}** **[Removed](https://gitlab.com/gitlab-org/gitlab/-/issues/293595)** in GitLab 14.0. Set to `true` to require domain validation when running DAST full scans. Not supported for API scans. Default: `false` |
|
||||
| `DAST_AUTO_UPDATE_ADDONS` | boolean | ZAP add-ons are pinned to specific versions in the DAST Docker image. Set to `true` to download the latest versions when the scan starts. Default: `false` |
|
||||
| `DAST_API_HOST_OVERRIDE` <sup>1</sup> | string | Used to override domains defined in API specification files. Only supported when importing the API specification from a URL. Example: `example.com:8080` |
|
||||
| `DAST_EXCLUDE_RULES` | string | Set to a comma-separated list of Vulnerability Rule IDs to exclude them from running during the scan. Rule IDs are numbers and can be found from the DAST log or on the [ZAP project](https://www.zaproxy.org/docs/alerts/). For example, `HTTP Parameter Override` has a rule ID of `10026`. Cannot be used when `DAST_ONLY_INCLUDE_RULES` is set. **Note:** In earlier versions of GitLab the excluded rules were executed but vulnerabilities they generated were suppressed. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118641) in GitLab 12.10. |
|
||||
| `DAST_ONLY_INCLUDE_RULES` | string | Set to a comma-separated list of Vulnerability Rule IDs to configure the scan to run only them. Rule IDs are numbers and can be found from the DAST log or on the [ZAP project](https://www.zaproxy.org/docs/alerts/). Cannot be used when `DAST_EXCLUDE_RULES` is set. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/250651) in GitLab 13.12. |
|
||||
| `DAST_REQUEST_HEADERS` <sup>1</sup> | string | Set to a comma-separated list of request header names and values. Headers are added to every request made by DAST. For example, `Cache-control: no-cache,User-Agent: DAST/1.0` |
|
||||
|:-------------------------------------------------|:--------------|:------------------------------|
|
||||
| `DAST_ADVERTISE_SCAN` | boolean | Set to `true` to add a `Via` header to every request sent, advertising that the request was sent as part of a GitLab DAST scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/334947) in GitLab 14.1. |
|
||||
| `DAST_AGGREGATE_VULNERABILITIES` | boolean | Vulnerability aggregation is set to `true` by default. To disable this feature and see each vulnerability individually set to `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/254043) in GitLab 14.0. |
|
||||
| `DAST_API_HOST_OVERRIDE` <sup>1</sup> | string | Used to override domains defined in API specification files. Only supported when importing the API specification from a URL. Example: `example.com:8080`. |
|
||||
| `DAST_API_OPENAPI` | URL or string | The API specification to import. The specification can be hosted at a URL, or the name of a file present in the `/zap/wrk` directory. The variable `DAST_WEBSITE` must be specified if this is omitted. |
|
||||
| `DAST_API_SPECIFICATION` <sup>1</sup> | URL or string | [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/290241) in GitLab 13.12 and replaced by `DAST_API_OPENAPI`. To be removed in GitLab 15.0. The API specification to import. The specification can be hosted at a URL, or the name of a file present in the `/zap/wrk` directory. The variable `DAST_WEBSITE` must be specified if this is omitted. |
|
||||
| `DAST_AUTH_REPORT` <sup>2</sup> | boolean | Used in combination with exporting the `gl-dast-debug-auth-report.html` artifact to aid in debugging authentication issues. |
|
||||
| `DAST_AUTH_EXCLUDE_URLS` <sup>2</sup> | URLs | **{warning}** **[Removed](https://gitlab.com/gitlab-org/gitlab/-/issues/289959)** in GitLab 14.0. Replaced by `DAST_EXCLUDE_URLS`. The URLs to skip during the authenticated scan; comma-separated. Regular expression syntax can be used to match multiple URLs. For example, `.*` matches an arbitrary character sequence. Not supported for API scans. |
|
||||
| `DAST_AUTH_URL` <sup>1,2</sup> | URL | The URL of the page containing the sign-in HTML form on the target website. `DAST_USERNAME` and `DAST_PASSWORD` are submitted with the login form to create an authenticated scan. Not supported for API scans. Example: `https://login.example.com`. |
|
||||
| `DAST_AUTH_VERIFICATION_LOGIN_FORM` <sup>2</sup> | boolean | Verifies successful authentication by checking for the lack of a login form once the login form has been submitted. |
|
||||
| `DAST_AUTH_VERIFICATION_SELECTOR` <sup>2</sup> | selector | Verifies successful authentication by checking for presence of a selector once the login form has been submitted. Example: `css:.user-photo`. |
|
||||
| `DAST_AUTH_VERIFICATION_URL` <sup>1,2</sup> | URL | A URL only accessible to logged in users that DAST can use to confirm successful authentication. If provided, DAST exits if it cannot access the URL. Example: `"http://example.com/loggedin_page"`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/207335) in GitLab 13.8. |
|
||||
| `DAST_AUTO_UPDATE_ADDONS` | boolean | ZAP add-ons are pinned to specific versions in the DAST Docker image. Set to `true` to download the latest versions when the scan starts. Default: `false`. |
|
||||
| `DAST_BROWSER_PATH_TO_LOGIN_FORM` <sup>1,2</sup> | selector | Comma-separated list of selectors that will be clicked on prior to attempting to enter `DAST_USERNAME` and `DAST_PASSWORD` into the login form. Example: `"css:.navigation-menu,css:.login-menu-item"`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/326633) in GitLab 14.1. |
|
||||
| `DAST_DEBUG` <sup>1</sup> | boolean | Enable debug message output. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
|
||||
| `DAST_TARGET_AVAILABILITY_TIMEOUT` <sup>1</sup> | number | Time limit in seconds to wait for target availability. |
|
||||
| `DAST_SPIDER_MINS` <sup>1</sup> | number | The maximum duration of the spider scan in minutes. Set to `0` for unlimited. Default: One minute, or unlimited when the scan is a full scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
|
||||
| `DAST_EXCLUDE_RULES` | string | Set to a comma-separated list of Vulnerability Rule IDs to exclude them from running during the scan. Rule IDs are numbers and can be found from the DAST log or on the [ZAP project](https://www.zaproxy.org/docs/alerts/). For example, `HTTP Parameter Override` has a rule ID of `10026`. Cannot be used when `DAST_ONLY_INCLUDE_RULES` is set. **Note:** In earlier versions of GitLab the excluded rules were executed but vulnerabilities they generated were suppressed. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118641) in GitLab 12.10. |
|
||||
| `DAST_EXCLUDE_URLS` <sup>1,2</sup> | URLs | The URLs to skip during the authenticated scan; comma-separated. Regular expression syntax can be used to match multiple URLs. For example, `.*` matches an arbitrary character sequence. Not supported for API scans. Example, `http://example.com/sign-out`. |
|
||||
| `DAST_FIRST_SUBMIT_FIELD` <sup>2</sup> | string | The `id` or `name` of the element that when clicked submits the username form of a multi-page login process. For example, `css:button[type='user-submit']`. [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9894) in GitLab 12.4. |
|
||||
| `DAST_FULL_SCAN_DOMAIN_VALIDATION_REQUIRED` | boolean | **{warning}** **[Removed](https://gitlab.com/gitlab-org/gitlab/-/issues/293595)** in GitLab 14.0. Set to `true` to require domain validation when running DAST full scans. Not supported for API scans. Default: `false` |
|
||||
| `DAST_FULL_SCAN_ENABLED` <sup>1</sup> | boolean | Set to `true` to run a [ZAP Full Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Full-Scan) instead of a [ZAP Baseline Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Baseline-Scan). Default: `false` |
|
||||
| `DAST_HTML_REPORT` | string | The filename of the HTML report written at the end of a scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
|
||||
| `DAST_MARKDOWN_REPORT` | string | The filename of the Markdown report written at the end of a scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
|
||||
| `DAST_XML_REPORT` | string | The filename of the XML report written at the end of a scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
|
||||
| `DAST_INCLUDE_ALPHA_VULNERABILITIES` | boolean | Set to `true` to include alpha passive and active scan rules. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
|
||||
| `DAST_USE_AJAX_SPIDER` <sup>1</sup> | boolean | Set to `true` to use the AJAX spider in addition to the traditional spider, useful for crawling sites that require JavaScript. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
|
||||
| `DAST_MARKDOWN_REPORT` | string | The filename of the Markdown report written at the end of a scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
|
||||
| `DAST_MASK_HTTP_HEADERS` | string | Comma-separated list of request and response headers to be masked (GitLab 13.1). Must contain **all** headers to be masked. Refer to [list of headers that are masked by default](#hide-sensitive-information). |
|
||||
| `DAST_MAX_URLS_PER_VULNERABILITY` | number | The maximum number of URLs reported for a single vulnerability. `DAST_MAX_URLS_PER_VULNERABILITY` is set to `50` by default. To list all the URLs set to `0`. [Introduced](https://gitlab.com/gitlab-org/security-products/dast/-/merge_requests/433) in GitLab 13.12. |
|
||||
| `DAST_ONLY_INCLUDE_RULES` | string | Set to a comma-separated list of Vulnerability Rule IDs to configure the scan to run only them. Rule IDs are numbers and can be found from the DAST log or on the [ZAP project](https://www.zaproxy.org/docs/alerts/). Cannot be used when `DAST_EXCLUDE_RULES` is set. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/250651) in GitLab 13.12. |
|
||||
| `DAST_PASSWORD` <sup>1,2</sup> | string | The password to authenticate to in the website. Example: `P@55w0rd!` |
|
||||
| `DAST_PASSWORD_FIELD` <sup>1,2</sup> | string | The selector of password field at the sign-in HTML form. Example: `id:password` |
|
||||
| `DAST_PATHS` | string | Set to a comma-separated list of URLs for DAST to scan. For example, `/page1.html,/category1/page3.html,/page2.html`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214120) in GitLab 13.4. |
|
||||
| `DAST_PATHS_FILE` | string | The file path containing the paths within `DAST_WEBSITE` to scan. The file must be plain text with one path per line. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/258825) in GitLab 13.6. |
|
||||
| `DAST_SUBMIT_FIELD` | string | The `id` or `name` of the element that when clicked submits the login form or the password form of a multi-page login process. [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9894) in GitLab 12.4. |
|
||||
| `DAST_FIRST_SUBMIT_FIELD` | string | The `id` or `name` of the element that when clicked submits the username form of a multi-page login process. [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9894) in GitLab 12.4. |
|
||||
| `DAST_REQUEST_HEADERS` <sup>1</sup> | string | Set to a comma-separated list of request header names and values. Headers are added to every request made by DAST. For example, `Cache-control: no-cache,User-Agent: DAST/1.0` |
|
||||
| `DAST_SKIP_TARGET_CHECK` | boolean | Set to `true` to prevent DAST from checking that the target is available before scanning. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229067) in GitLab 13.8. |
|
||||
| `DAST_SPIDER_MINS` <sup>1</sup> | number | The maximum duration of the spider scan in minutes. Set to `0` for unlimited. Default: One minute, or unlimited when the scan is a full scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
|
||||
| `DAST_SPIDER_START_AT_HOST` | boolean | Set to `false` to prevent DAST from resetting the target to its host before scanning. When `true`, non-host targets `http://test.site/some_path` is reset to `http://test.site` before scan. Default: `true`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/258805) in GitLab 13.6. |
|
||||
| `DAST_SUBMIT_FIELD` <sup>2</sup> | string | The `id` or `name` of the element that when clicked submits the login form or the password form of a multi-page login process. For example, `css:button[type='submit']`. [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9894) in GitLab 12.4. |
|
||||
| `DAST_TARGET_AVAILABILITY_TIMEOUT` <sup>1</sup> | number | Time limit in seconds to wait for target availability. |
|
||||
| `DAST_USE_AJAX_SPIDER` <sup>1</sup> | boolean | Set to `true` to use the AJAX spider in addition to the traditional spider, useful for crawling sites that require JavaScript. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
|
||||
| `DAST_USERNAME` <sup>1,2</sup> | string | The username to authenticate to in the website. Example: `admin` |
|
||||
| `DAST_USERNAME_FIELD` <sup>1,2</sup> | string | The selector of username field at the sign-in HTML form. Example: `name:username` |
|
||||
| `DAST_XML_REPORT` | string | The filename of the XML report written at the end of a scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
|
||||
| `DAST_WEBSITE` <sup>1</sup> | URL | The URL of the website to scan. The variable `DAST_API_OPENAPI` must be specified if this is omitted. |
|
||||
| `DAST_ZAP_CLI_OPTIONS` | string | ZAP server command-line options. For example, `-Xmx3072m` would set the Java maximum memory allocation pool size. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
|
||||
| `DAST_ZAP_LOG_CONFIGURATION` | string | Set to a semicolon-separated list of additional log4j properties for the ZAP Server. For example, `log4j.logger.org.parosproxy.paros.network.HttpSender=DEBUG;log4j.logger.com.crawljax=DEBUG` |
|
||||
| `DAST_AUTH_EXCLUDE_URLS` | URLs | **{warning}** **[Removed](https://gitlab.com/gitlab-org/gitlab/-/issues/289959)** in GitLab 14.0. Replaced by `DAST_EXCLUDE_URLS`. The URLs to skip during the authenticated scan; comma-separated. Regular expression syntax can be used to match multiple URLs. For example, `.*` matches an arbitrary character sequence. Not supported for API scans. |
|
||||
| `DAST_AGGREGATE_VULNERABILITIES` | boolean | Vulnerability aggregation is set to `true` by default. To disable this feature and see each vulnerability individually set to `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/254043) in GitLab 14.0. |
|
||||
| `DAST_MAX_URLS_PER_VULNERABILITY` | number | The maximum number of URLs reported for a single vulnerability. `DAST_MAX_URLS_PER_VULNERABILITY` is set to `50` by default. To list all the URLs set to `0`. [Introduced](https://gitlab.com/gitlab-org/security-products/dast/-/merge_requests/433) in GitLab 13.12. |
|
||||
| `DAST_AUTH_REPORT` | boolean | Used in combination with exporting the `gl-dast-debug-auth-report.html` artifact to aid in debugging authentication issues. |
|
||||
| `DAST_AUTH_VERIFICATION_URL` <sup>1</sup> | URL | A URL only accessible to logged in users that DAST can use to confirm successful authentication. If provided, DAST exits if it cannot access the URL. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/207335) in GitLab 13.8. |
|
||||
| `DAST_AUTH_VERIFICATION_SELECTOR` | selector | Verifies successful authentication by checking for presence of a selector once the login form has been submitted. Example: `css:.user-photo` |
|
||||
| `DAST_AUTH_VERIFICATION_LOGIN_FORM` | boolean | Verifies successful authentication by checking for the lack of a login form once the login form has been submitted. |
|
||||
| `DAST_ADVERTISE_SCAN` | boolean | Set to `true` to add a `Via` header to every request sent, advertising that the request was sent as part of a GitLab DAST scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/334947) in GitLab 14.1. |
|
||||
| `DAST_ZAP_LOG_CONFIGURATION` | string | Set to a semicolon-separated list of additional log4j properties for the ZAP Server. Example: `log4j.logger.org.parosproxy.paros.network.HttpSender=DEBUG;log4j.logger.com.crawljax=DEBUG` |
|
||||
| `SECURE_ANALYZERS_PREFIX` | URL | Set the Docker registry base address from which to download the analyzer. |
|
||||
|
||||
1. Available to an on-demand DAST scan.
|
||||
1. Used for authentication.
|
||||
|
||||
#### Selectors
|
||||
|
||||
|
|
|
@ -45,10 +45,12 @@ The following table lists project permissions available for each role:
|
|||
|
||||
| Action | Guest | Reporter | Developer |Maintainer| Owner |
|
||||
|---------------------------------------------------|---------|------------|-------------|----------|--------|
|
||||
| Assign issues | ✓ (*16*)| ✓ | ✓ | ✓ | ✓ |
|
||||
| Create confidential issue | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Create new issue | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Download and browse job artifacts | ✓ (*3*) | ✓ | ✓ | ✓ | ✓ |
|
||||
| Download project | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
|
||||
| Label issues | ✓ (*16*)| ✓ | ✓ | ✓ | ✓ |
|
||||
| Leave comments | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Manage user-starred metrics dashboards (*7*) | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Pull project code | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
|
||||
|
@ -56,6 +58,7 @@ The following table lists project permissions available for each role:
|
|||
| See a job log | ✓ (*3*) | ✓ | ✓ | ✓ | ✓ |
|
||||
| See a list of jobs | ✓ (*3*) | ✓ | ✓ | ✓ | ✓ |
|
||||
| See linked issues | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Set issue weight | ✓ (*16*)| ✓ | ✓ | ✓ | ✓ |
|
||||
| View [Design Management](project/issues/design_management.md) pages | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| View [Releases](project/releases/index.md) | ✓ (*6*) | ✓ | ✓ | ✓ | ✓ |
|
||||
| View a time tracking report | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
|
||||
|
@ -73,13 +76,11 @@ The following table lists project permissions available for each role:
|
|||
| View wiki pages | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Archive [test case](../ci/test_cases/index.md) | | ✓ | ✓ | ✓ | ✓ |
|
||||
| Archive/reopen requirements **(ULTIMATE)** | | ✓ | ✓ | ✓ | ✓ |
|
||||
| Assign issues | | ✓ | ✓ | ✓ | ✓ |
|
||||
| Assign reviewers | | ✓ | ✓ | ✓ | ✓ |
|
||||
| Create code snippets | | ✓ | ✓ | ✓ | ✓ |
|
||||
| Create new [test case](../ci/test_cases/index.md) | | ✓ | ✓ | ✓ | ✓ |
|
||||
| Create/edit requirements **(ULTIMATE)** | | ✓ | ✓ | ✓ | ✓ |
|
||||
| Import/export requirements **(ULTIMATE)** | | ✓ | ✓ | ✓ | ✓ |
|
||||
| Label issues | | ✓ | ✓ | ✓ | ✓ |
|
||||
| Lock issue threads | | ✓ | ✓ | ✓ | ✓ |
|
||||
| Manage issue tracker | | ✓ | ✓ | ✓ | ✓ |
|
||||
| Manage labels | | ✓ | ✓ | ✓ | ✓ |
|
||||
|
@ -94,7 +95,6 @@ The following table lists project permissions available for each role:
|
|||
| See a list of merge requests | | ✓ | ✓ | ✓ | ✓ |
|
||||
| See environments | | ✓ | ✓ | ✓ | ✓ |
|
||||
| [Set issue estimate and record time spent](project/time_tracking.md) | | ✓ | ✓ | ✓ | ✓ |
|
||||
| Set issue weight | | ✓ | ✓ | ✓ | ✓ |
|
||||
| View CI/CD analytics | | ✓ | ✓ | ✓ | ✓ |
|
||||
| View Code Review analytics **(PREMIUM)** | | ✓ | ✓ | ✓ | ✓ |
|
||||
| View confidential issues | (*2*) | ✓ | ✓ | ✓ | ✓ |
|
||||
|
@ -216,6 +216,7 @@ The following table lists project permissions available for each role:
|
|||
[project visibility](../public_access/public_access.md) is set to private.
|
||||
1. Attached design files are moved together with the issue even if the user doesn't have the
|
||||
Developer role.
|
||||
1. Guest users can set metadata (for example, labels, assignees, or milestones) when creating an issue.
|
||||
|
||||
## Project features permissions
|
||||
|
||||
|
|
|
@ -5787,6 +5787,9 @@ msgstr ""
|
|||
msgid "CI configuration validated, including all configuration added with the %{codeStart}includes%{codeEnd} keyword. %{link}"
|
||||
msgstr ""
|
||||
|
||||
msgid "CI minutes"
|
||||
msgstr ""
|
||||
|
||||
msgid "CI settings"
|
||||
msgstr ""
|
||||
|
||||
|
@ -37732,6 +37735,9 @@ msgstr ""
|
|||
msgid "You have set up 2FA for your account! If you lose access to your 2FA device, you can use your recovery codes to access your account. Alternatively, if you upload an SSH key, you can %{anchorOpen}use that key to generate additional recovery codes%{anchorClose}."
|
||||
msgstr ""
|
||||
|
||||
msgid "You have successfully purchased %{product}. You'll receive a receipt by mail."
|
||||
msgstr ""
|
||||
|
||||
msgid "You have successfully purchased a %{plan} plan subscription for %{seats}. You’ll receive a receipt via email."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -7,9 +7,13 @@ RSpec.describe JiraConnect::SubscriptionsController do
|
|||
|
||||
describe '#index' do
|
||||
before do
|
||||
request.headers['Accept'] = content_type
|
||||
|
||||
get :index, params: { jwt: jwt }
|
||||
end
|
||||
|
||||
let(:content_type) { 'text/html' }
|
||||
|
||||
context 'without JWT' do
|
||||
let(:jwt) { nil }
|
||||
|
||||
|
@ -29,13 +33,55 @@ RSpec.describe JiraConnect::SubscriptionsController do
|
|||
it 'removes X-Frame-Options to allow rendering in iframe' do
|
||||
expect(response.headers['X-Frame-Options']).to be_nil
|
||||
end
|
||||
|
||||
context 'with JSON format' do
|
||||
let_it_be(:subscription) { create(:jira_connect_subscription, installation: installation) }
|
||||
|
||||
let(:content_type) { 'application/json' }
|
||||
|
||||
it 'renders the relevant data as JSON', :aggregate_failures do
|
||||
expect(json_response).to include('groups_path' => api_v4_groups_path(params: { min_access_level: Gitlab::Access::MAINTAINER, skip_groups: [subscription.namespace_id] }))
|
||||
expect(json_response).to include(
|
||||
'subscriptions' => [
|
||||
'group' => {
|
||||
'name' => subscription.namespace.name,
|
||||
'avatar_url' => subscription.namespace.avatar_url,
|
||||
'full_name' => subscription.namespace.full_name,
|
||||
'description' => subscription.namespace.description
|
||||
},
|
||||
'created_at' => subscription.created_at.iso8601(3),
|
||||
'unlink_path' => jira_connect_subscription_path(subscription)
|
||||
]
|
||||
)
|
||||
expect(json_response).to include('subscriptions_path' => jira_connect_subscriptions_path)
|
||||
end
|
||||
|
||||
context 'when not signed in to GitLab' do
|
||||
it 'contains a login path' do
|
||||
expect(json_response).to include('login_path' => jira_connect_users_path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when signed in to GitLab' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
|
||||
get :index, params: { jwt: jwt }
|
||||
end
|
||||
|
||||
it 'does not contain a login path' do
|
||||
expect(json_response).to include('login_path' => nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#create' do
|
||||
let(:group) { create(:group) }
|
||||
let(:user) { create(:user) }
|
||||
let(:current_user) { user }
|
||||
|
||||
before do
|
||||
group.add_maintainer(user)
|
||||
|
|
|
@ -174,6 +174,8 @@ RSpec.describe Projects::ServicesController do
|
|||
let(:redirect_url) { edit_project_service_path(project, integration) }
|
||||
|
||||
before do
|
||||
stub_jira_integration_test
|
||||
|
||||
put :update, params: params
|
||||
end
|
||||
|
||||
|
@ -222,12 +224,48 @@ RSpec.describe Projects::ServicesController do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when param `inherit_from_id` is set to some value' do
|
||||
let(:instance_service) { create(:jira_integration, :instance) }
|
||||
let(:integration_params) { { inherit_from_id: instance_service.id } }
|
||||
context 'when param `inherit_from_id` is set to an instance integration' do
|
||||
let(:instance_integration) { create(:jira_integration, :instance, url: 'http://instance.com', password: 'instance') }
|
||||
let(:integration_params) { { inherit_from_id: instance_integration.id, url: 'http://custom.com', password: 'custom' } }
|
||||
|
||||
it 'sets inherit_from_id to value' do
|
||||
expect(integration.reload.inherit_from_id).to eq(instance_service.id)
|
||||
it 'ignores submitted params and inherits instance settings' do
|
||||
expect(integration.reload).to have_attributes(
|
||||
inherit_from_id: instance_integration.id,
|
||||
url: instance_integration.url,
|
||||
password: instance_integration.password
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when param `inherit_from_id` is set to a group integration' do
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, group: group) }
|
||||
let_it_be(:jira_integration) { create(:jira_integration, project: project) }
|
||||
|
||||
let(:group_integration) { create(:jira_integration, group: group, project: nil, url: 'http://group.com', password: 'group') }
|
||||
let(:integration_params) { { inherit_from_id: group_integration.id, url: 'http://custom.com', password: 'custom' } }
|
||||
|
||||
it 'ignores submitted params and inherits group settings' do
|
||||
expect(integration.reload).to have_attributes(
|
||||
inherit_from_id: group_integration.id,
|
||||
url: group_integration.url,
|
||||
password: group_integration.password
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when param `inherit_from_id` is set to an unrelated group' do
|
||||
let_it_be(:group) { create(:group) }
|
||||
|
||||
let(:group_integration) { create(:jira_integration, group: group, project: nil, url: 'http://group.com', password: 'group') }
|
||||
let(:integration_params) { { inherit_from_id: group_integration.id, url: 'http://custom.com', password: 'custom' } }
|
||||
|
||||
it 'ignores the param and saves the submitted settings' do
|
||||
expect(integration.reload).to have_attributes(
|
||||
inherit_from_id: nil,
|
||||
url: 'http://custom.com',
|
||||
password: 'custom'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'User uses inherited settings', :js do
|
||||
include JiraServiceHelper
|
||||
|
||||
include_context 'project service activation'
|
||||
|
||||
before do
|
||||
stub_jira_integration_test
|
||||
end
|
||||
|
||||
shared_examples 'inherited settings' do
|
||||
let_it_be(:project_settings) { { url: 'http://project.com', password: 'project' } }
|
||||
|
||||
describe 'switching from inherited to custom settings' do
|
||||
let_it_be(:integration) { create(:jira_integration, project: project, inherit_from_id: parent_integration.id, **project_settings) }
|
||||
|
||||
it 'clears the form fields and saves the entered values' do
|
||||
visit_project_integration('Jira')
|
||||
|
||||
expect(page).not_to have_button('Use custom settings')
|
||||
expect(page).to have_field('Web URL', with: parent_settings[:url], readonly: true)
|
||||
expect(page).to have_field('Enter new password or API token', with: '', readonly: true)
|
||||
|
||||
click_on 'Use default settings'
|
||||
click_on 'Use custom settings'
|
||||
|
||||
expect(page).not_to have_button('Use default settings')
|
||||
expect(page).to have_field('Web URL', with: project_settings[:url], readonly: false)
|
||||
expect(page).to have_field('Enter new password or API token', with: '', readonly: false)
|
||||
|
||||
fill_in 'Web URL', with: 'http://custom.com'
|
||||
fill_in 'Enter new password or API token', with: 'custom'
|
||||
|
||||
click_save_integration
|
||||
|
||||
expect(page).to have_text('Jira settings saved and active.')
|
||||
expect(integration.reload).to have_attributes(
|
||||
inherit_from_id: nil,
|
||||
url: 'http://custom.com',
|
||||
password: 'custom'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'switching from custom to inherited settings' do
|
||||
let_it_be(:integration) { create(:jira_integration, project: project, **project_settings) }
|
||||
|
||||
it 'resets the form fields, makes them read-only, and saves the inherited values' do
|
||||
visit_project_integration('Jira')
|
||||
|
||||
expect(page).not_to have_button('Use default settings')
|
||||
expect(page).to have_field('URL', with: project_settings[:url], readonly: false)
|
||||
expect(page).to have_field('Enter new password or API token', with: '', readonly: false)
|
||||
|
||||
click_on 'Use custom settings'
|
||||
click_on 'Use default settings'
|
||||
|
||||
expect(page).not_to have_button('Use custom settings')
|
||||
expect(page).to have_field('URL', with: parent_settings[:url], readonly: true)
|
||||
expect(page).to have_field('Enter new password or API token', with: '', readonly: true)
|
||||
|
||||
click_save_integration
|
||||
|
||||
expect(page).to have_text('Jira settings saved and active.')
|
||||
expect(integration.reload).to have_attributes(
|
||||
inherit_from_id: parent_integration.id,
|
||||
**parent_settings
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with instance settings' do
|
||||
let_it_be(:parent_settings) { { url: 'http://instance.com', password: 'instance' } }
|
||||
let_it_be(:parent_integration) { create(:jira_integration, :instance, **parent_settings) }
|
||||
|
||||
it_behaves_like 'inherited settings'
|
||||
end
|
||||
|
||||
context 'with group settings' do
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, group: group) }
|
||||
let_it_be(:parent_settings) { { url: 'http://group.com', password: 'group' } }
|
||||
let_it_be(:parent_integration) { create(:jira_integration, group: group, project: nil, **parent_settings) }
|
||||
|
||||
it_behaves_like 'inherited settings'
|
||||
end
|
||||
end
|
|
@ -57,6 +57,56 @@ RSpec.describe 'User page' do
|
|||
end
|
||||
end
|
||||
|
||||
context 'location' do
|
||||
let_it_be(:location) { 'San Francisco, CA' }
|
||||
|
||||
context 'when location is set' do
|
||||
let_it_be(:user) { create(:user, location: location) }
|
||||
|
||||
it 'shows location' do
|
||||
subject
|
||||
|
||||
expect(page).to have_content(location)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when location is not set' do
|
||||
it 'does not show location' do
|
||||
subject
|
||||
|
||||
expect(page).not_to have_content(location)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'timezone' do
|
||||
let_it_be(:timezone) { 'America/Los_Angeles' }
|
||||
|
||||
before do
|
||||
travel_to Time.find_zone(timezone).local(2021, 7, 20, 15, 30, 45)
|
||||
end
|
||||
|
||||
context 'when timezone is set' do
|
||||
let_it_be(:user) { create(:user, timezone: timezone) }
|
||||
|
||||
it 'shows local time' do
|
||||
subject
|
||||
|
||||
expect(page).to have_content('3:30 PM')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when timezone is invalid' do
|
||||
let_it_be(:user) { create(:user, timezone: 'Foo/Bar') }
|
||||
|
||||
it 'shows local time using the configured default timezone (UTC in this case)' do
|
||||
subject
|
||||
|
||||
expect(page).to have_content('10:30 PM')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'follow/unfollow and followers/following' do
|
||||
let_it_be(:followee) { create(:user) }
|
||||
let_it_be(:follower) { create(:user) }
|
||||
|
|
|
@ -6,10 +6,11 @@ import waitForPromises from 'helpers/wait_for_promises';
|
|||
import createFlash from '~/flash';
|
||||
|
||||
import PackagesApp from '~/packages_and_registries/package_registry/components/details/app.vue';
|
||||
import PackageHistory from '~/packages_and_registries/package_registry/components/details/package_history.vue';
|
||||
import PackageTitle from '~/packages_and_registries/package_registry/components/details/package_title.vue';
|
||||
import { FETCH_PACKAGE_DETAILS_ERROR_MESSAGE } from '~/packages_and_registries/package_registry/constants';
|
||||
import getPackageDetails from '~/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql';
|
||||
import { packageDetailsQuery, packageData } from '../../mock_data';
|
||||
import { packageDetailsQuery, packageData, emptyPackageDetailsQuery } from '../../mock_data';
|
||||
|
||||
jest.mock('~/flash');
|
||||
|
||||
|
@ -19,6 +20,18 @@ describe('PackagesApp', () => {
|
|||
let wrapper;
|
||||
let apolloProvider;
|
||||
|
||||
const provide = {
|
||||
packageId: '111',
|
||||
titleComponent: 'PackageTitle',
|
||||
projectName: 'projectName',
|
||||
canDelete: 'canDelete',
|
||||
svgPath: 'svgPath',
|
||||
npmPath: 'npmPath',
|
||||
npmHelpPath: 'npmHelpPath',
|
||||
projectListUrl: 'projectListUrl',
|
||||
groupListUrl: 'groupListUrl',
|
||||
};
|
||||
|
||||
function createComponent({ resolver = jest.fn().mockResolvedValue(packageDetailsQuery()) } = {}) {
|
||||
localVue.use(VueApollo);
|
||||
|
||||
|
@ -28,29 +41,22 @@ describe('PackagesApp', () => {
|
|||
wrapper = shallowMount(PackagesApp, {
|
||||
localVue,
|
||||
apolloProvider,
|
||||
provide: {
|
||||
packageId: '111',
|
||||
titleComponent: 'PackageTitle',
|
||||
projectName: 'projectName',
|
||||
canDelete: 'canDelete',
|
||||
svgPath: 'svgPath',
|
||||
npmPath: 'npmPath',
|
||||
npmHelpPath: 'npmHelpPath',
|
||||
projectListUrl: 'projectListUrl',
|
||||
groupListUrl: 'groupListUrl',
|
||||
},
|
||||
provide,
|
||||
});
|
||||
}
|
||||
|
||||
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
|
||||
const findPackageTitle = () => wrapper.findComponent(PackageTitle);
|
||||
const findPackageHistory = () => wrapper.findComponent(PackageHistory);
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('renders an empty state component', () => {
|
||||
createComponent();
|
||||
it('renders an empty state component', async () => {
|
||||
createComponent({ resolver: jest.fn().mockResolvedValue(emptyPackageDetailsQuery) });
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findEmptyState().exists()).toBe(true);
|
||||
});
|
||||
|
@ -77,4 +83,16 @@ describe('PackagesApp', () => {
|
|||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('renders history and has the right props', async () => {
|
||||
createComponent();
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findPackageHistory().exists()).toBe(true);
|
||||
expect(findPackageHistory().props()).toMatchObject({
|
||||
packageEntity: expect.objectContaining(packageData()),
|
||||
projectName: provide.projectName,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import { GlLink, GlSprintf } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
import { mavenPackage, mockPipelineInfo } from 'jest/packages/mock_data';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import {
|
||||
packageData,
|
||||
packagePipelines,
|
||||
} from 'jest/packages_and_registries/package_registry/mock_data';
|
||||
import { HISTORY_PIPELINES_LIMIT } from '~/packages/details/constants';
|
||||
import component from '~/packages_and_registries/package_registry/components/details/package_history.vue';
|
||||
import HistoryItem from '~/vue_shared/components/registry/history_item.vue';
|
||||
|
@ -11,14 +14,16 @@ describe('Package History', () => {
|
|||
let wrapper;
|
||||
const defaultProps = {
|
||||
projectName: 'baz project',
|
||||
packageEntity: { ...mavenPackage },
|
||||
packageEntity: { ...packageData() },
|
||||
};
|
||||
|
||||
const [onePipeline] = packagePipelines();
|
||||
|
||||
const createPipelines = (amount) =>
|
||||
[...Array(amount)].map((x, index) => ({ ...mockPipelineInfo, id: index + 1 }));
|
||||
[...Array(amount)].map((x, index) => packagePipelines({ id: index + 1 })[0]);
|
||||
|
||||
const mountComponent = (props) => {
|
||||
wrapper = shallowMount(component, {
|
||||
wrapper = shallowMountExtended(component, {
|
||||
propsData: { ...defaultProps, ...props },
|
||||
stubs: {
|
||||
HistoryItem: stubComponent(HistoryItem, {
|
||||
|
@ -31,14 +36,13 @@ describe('Package History', () => {
|
|||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
const findHistoryElement = (testId) => wrapper.find(`[data-testid="${testId}"]`);
|
||||
const findElementLink = (container) => container.find(GlLink);
|
||||
const findElementTimeAgo = (container) => container.find(TimeAgoTooltip);
|
||||
const findTitle = () => wrapper.find('[data-testid="title"]');
|
||||
const findTimeline = () => wrapper.find('[data-testid="timeline"]');
|
||||
const findHistoryElement = (testId) => wrapper.findByTestId(testId);
|
||||
const findElementLink = (container) => container.findComponent(GlLink);
|
||||
const findElementTimeAgo = (container) => container.findComponent(TimeAgoTooltip);
|
||||
const findTitle = () => wrapper.findByTestId('title');
|
||||
const findTimeline = () => wrapper.findByTestId('timeline');
|
||||
|
||||
it('has the correct title', () => {
|
||||
mountComponent();
|
||||
|
@ -59,23 +63,25 @@ describe('Package History', () => {
|
|||
expect.arrayContaining(['timeline', 'main-notes-list', 'notes']),
|
||||
);
|
||||
});
|
||||
|
||||
describe.each`
|
||||
name | amount | icon | text | timeAgoTooltip | link
|
||||
${'created-on'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'clock'} | ${'Test package version 1.0.0 was first created'} | ${mavenPackage.created_at} | ${null}
|
||||
${'first-pipeline-commit'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'commit'} | ${'Created by commit #sha-baz on branch branch-name'} | ${null} | ${mockPipelineInfo.project.commit_url}
|
||||
${'first-pipeline-pipeline'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'pipeline'} | ${'Built by pipeline #1 triggered by foo'} | ${mockPipelineInfo.created_at} | ${mockPipelineInfo.project.pipeline_url}
|
||||
${'published'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'package'} | ${'Published to the baz project Package Registry'} | ${mavenPackage.created_at} | ${null}
|
||||
${'created-on'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'clock'} | ${'@gitlab-org/package-15 version 1.0.0 was first created'} | ${packageData().createdAt} | ${null}
|
||||
${'first-pipeline-commit'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'commit'} | ${'Created by commit #b83d6e39 on branch master'} | ${null} | ${onePipeline.commitPath}
|
||||
${'first-pipeline-pipeline'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'pipeline'} | ${'Built by pipeline #1 triggered by Administrator'} | ${onePipeline.createdAt} | ${onePipeline.path}
|
||||
${'published'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'package'} | ${'Published to the baz project Package Registry'} | ${packageData().createdAt} | ${null}
|
||||
${'archived'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'history'} | ${'Package has 1 archived update'} | ${null} | ${null}
|
||||
${'archived'} | ${HISTORY_PIPELINES_LIMIT + 3} | ${'history'} | ${'Package has 2 archived updates'} | ${null} | ${null}
|
||||
${'pipeline-entry'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'pencil'} | ${'Package updated by commit #sha-baz on branch branch-name, built by pipeline #3, and published to the registry'} | ${mavenPackage.created_at} | ${mockPipelineInfo.project.commit_url}
|
||||
${'pipeline-entry'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'pencil'} | ${'Package updated by commit #b83d6e39 on branch master, built by pipeline #3, and published to the registry'} | ${packageData().createdAt} | ${onePipeline.commitPath}
|
||||
`(
|
||||
'with $amount pipelines history element $name',
|
||||
({ name, icon, text, timeAgoTooltip, link, amount }) => {
|
||||
let element;
|
||||
|
||||
beforeEach(() => {
|
||||
const packageEntity = { ...packageData(), pipelines: { nodes: createPipelines(amount) } };
|
||||
mountComponent({
|
||||
packageEntity: { ...mavenPackage, pipelines: createPipelines(amount) },
|
||||
packageEntity,
|
||||
});
|
||||
element = findHistoryElement(name);
|
||||
});
|
||||
|
|
|
@ -6,11 +6,21 @@ export const packageTags = () => [
|
|||
|
||||
export const packagePipelines = (extend) => [
|
||||
{
|
||||
commitPath: '/namespace14/project14/-/commit/b83d6e391c22777fca1ed3012fce84f633d7fed0',
|
||||
createdAt: '2020-08-17T14:23:32Z',
|
||||
id: 'gid://gitlab/Ci::Pipeline/36',
|
||||
path: '/namespace14/project14/-/pipelines/36',
|
||||
name: 'project14',
|
||||
ref: 'master',
|
||||
sha: 'b83d6e391c22777fca1ed3012fce84f633d7fed0',
|
||||
project: {
|
||||
name: 'project14',
|
||||
webUrl: 'http://gdk.test:3000/namespace14/project14',
|
||||
__typename: 'Project',
|
||||
},
|
||||
user: {
|
||||
name: 'Administrator',
|
||||
},
|
||||
...extend,
|
||||
__typename: 'Pipeline',
|
||||
},
|
||||
|
@ -68,3 +78,11 @@ export const packageDetailsQuery = () => ({
|
|||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const emptyPackageDetailsQuery = () => ({
|
||||
data: {
|
||||
package: {
|
||||
__typename: 'PackageDetailsType',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -68,4 +68,24 @@ RSpec.describe TimeZoneHelper, :aggregate_failures do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#local_time' do
|
||||
let_it_be(:timezone) { 'America/Los_Angeles' }
|
||||
|
||||
before do
|
||||
travel_to Time.find_zone(timezone).local(2021, 7, 20, 15, 30, 45)
|
||||
end
|
||||
|
||||
context 'when a valid timezone is passed' do
|
||||
it 'returns local time' do
|
||||
expect(helper.local_time(timezone)).to eq('3:30 PM')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an invalid timezone is passed' do
|
||||
it 'returns local time using the configured default timezone (UTC in this case)' do
|
||||
expect(helper.local_time('Foo/Bar')).to eq('10:30 PM')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,7 +4,7 @@ require 'spec_helper'
|
|||
|
||||
require_migration!
|
||||
|
||||
RSpec.describe ScheduleDeleteOrphanedDeployments, :sidekiq, schema: 20210617161348 do
|
||||
RSpec.describe RescheduleDeleteOrphanedDeployments, :sidekiq, schema: 20210617161348 do
|
||||
let!(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
|
||||
let!(:project) { table(:projects).create!(namespace_id: namespace.id) }
|
||||
let!(:environment) { table(:environments).create!(name: 'production', slug: 'production', project_id: project.id) }
|
||||
|
@ -22,6 +22,31 @@ RSpec.describe ScheduleDeleteOrphanedDeployments, :sidekiq, schema: 202106171613
|
|||
stub_const("#{described_class}::BATCH_SIZE", 1)
|
||||
end
|
||||
|
||||
it 'steal existing background migration jobs' do
|
||||
expect(Gitlab::BackgroundMigration).to receive(:steal).with('DeleteOrphanedDeployments')
|
||||
|
||||
migrate!
|
||||
end
|
||||
|
||||
it 'cleans up background migration jobs tracking records' do
|
||||
old_successful_job = background_migration_jobs.create!(
|
||||
class_name: 'DeleteOrphanedDeployments',
|
||||
status: Gitlab::Database::BackgroundMigrationJob.statuses[:succeeded],
|
||||
arguments: [table(:deployments).minimum(:id), table(:deployments).minimum(:id)]
|
||||
)
|
||||
|
||||
old_pending_job = background_migration_jobs.create!(
|
||||
class_name: 'DeleteOrphanedDeployments',
|
||||
status: Gitlab::Database::BackgroundMigrationJob.statuses[:pending],
|
||||
arguments: [table(:deployments).maximum(:id), table(:deployments).maximum(:id)]
|
||||
)
|
||||
|
||||
migrate!
|
||||
|
||||
expect { old_successful_job.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
expect { old_pending_job.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
|
||||
it 'schedules DeleteOrphanedDeployments background jobs' do
|
||||
Sidekiq::Testing.fake! do
|
||||
freeze_time do
|
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe JiraConnect::AppDataSerializer do
|
||||
describe '#as_json' do
|
||||
subject(:app_data_json) { described_class.new(subscriptions, signed_in).as_json }
|
||||
|
||||
let_it_be(:subscriptions) { create_list(:jira_connect_subscription, 2) }
|
||||
|
||||
let(:signed_in) { false }
|
||||
|
||||
it 'uses the subscription entity' do
|
||||
expect(JiraConnect::SubscriptionEntity).to receive(:represent).with(subscriptions)
|
||||
|
||||
app_data_json
|
||||
end
|
||||
|
||||
it 'includes a group path with already subscribed namespaces as skip_groups' do
|
||||
expected_path = "/api/v4/groups?min_access_level=40&skip_groups%5B%5D=#{subscriptions.first.namespace_id}&skip_groups%5B%5D=#{subscriptions.last.namespace_id}"
|
||||
|
||||
expect(app_data_json).to include(groups_path: expected_path)
|
||||
end
|
||||
|
||||
it { is_expected.to include(subscriptions_path: '/-/jira_connect/subscriptions') }
|
||||
it { is_expected.to include(login_path: '/-/jira_connect/users') }
|
||||
|
||||
context 'when signed in' do
|
||||
let(:signed_in) { true }
|
||||
|
||||
it { is_expected.to include(login_path: nil) }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe JiraConnect::GroupEntity do
|
||||
subject do
|
||||
described_class.new(subscription.namespace).as_json
|
||||
end
|
||||
|
||||
let(:subscription) { create(:jira_connect_subscription) }
|
||||
|
||||
it 'contains all necessary elements of the group', :aggregate_failures do
|
||||
expect(subject[:name]).to eq(subscription.namespace.name)
|
||||
expect(subject[:avatar_url]).to eq(subscription.namespace.avatar_url)
|
||||
expect(subject[:full_name]).to eq(subscription.namespace.full_name)
|
||||
expect(subject[:description]).to eq(subscription.namespace.description)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe JiraConnect::SubscriptionEntity do
|
||||
subject do
|
||||
described_class.new(subscription).as_json
|
||||
end
|
||||
|
||||
let(:subscription) { create(:jira_connect_subscription) }
|
||||
|
||||
it 'contains all necessary elements of the subscription', :aggregate_failures do
|
||||
expect(subject).to include(:created_at)
|
||||
expect(subject[:unlink_path]).to eq("/-/jira_connect/subscriptions/#{subscription.id}")
|
||||
expect(subject[:group]).to eq(
|
||||
name: subscription.namespace.name,
|
||||
avatar_url: subscription.namespace.avatar_url,
|
||||
full_name: subscription.namespace.full_name,
|
||||
description: subscription.namespace.description
|
||||
)
|
||||
end
|
||||
end
|
|
@ -55,6 +55,15 @@ RSpec.describe Boards::Issues::ListService do
|
|||
|
||||
it_behaves_like 'issues list service'
|
||||
end
|
||||
|
||||
context 'when filtering by type' do
|
||||
it 'only returns the specified type' do
|
||||
issue = create(:labeled_issue, project: project, milestone: m1, labels: [development, p1], issue_type: 'incident')
|
||||
params = { board_id: board.id, id: list1.id, issue_types: 'incident' }
|
||||
|
||||
expect(described_class.new(parent, user, params).execute).to eq [issue]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# rubocop: disable RSpec/MultipleMemoizedHelpers
|
||||
|
|
|
@ -76,4 +76,16 @@ RSpec.describe BulkUpdateIntegrationService do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'works with batch as an ActiveRecord::Relation' do
|
||||
expect do
|
||||
described_class.new(group_integration, Integration.where(id: integration.id)).execute
|
||||
end.to change { integration.reload.url }.to(group_integration.url)
|
||||
end
|
||||
|
||||
it 'works with batch as an array of ActiveRecord objects' do
|
||||
expect do
|
||||
described_class.new(group_integration, [integration]).execute
|
||||
end.to change { integration.reload.url }.to(group_integration.url)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
RSpec.shared_context 'project service activation' do
|
||||
include_context 'integration activation'
|
||||
|
||||
let(:project) { create(:project) }
|
||||
let(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
|
|
Loading…
Reference in New Issue