Commit 60530366 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 98f62680 5894d37d
...@@ -72,14 +72,6 @@ export default () => { ...@@ -72,14 +72,6 @@ export default () => {
boardsStore.setTimeTrackingLimitToHours($boardApp.dataset.timeTrackingLimitToHours); boardsStore.setTimeTrackingLimitToHours($boardApp.dataset.timeTrackingLimitToHours);
} }
if (gon?.features?.boardsFilteredSearch) {
import('~/boards/filtered_search')
.then(({ default: initFilteredSearch }) => {
initFilteredSearch(apolloProvider);
})
.catch(() => {});
}
// eslint-disable-next-line @gitlab/no-runtime-template-compiler // eslint-disable-next-line @gitlab/no-runtime-template-compiler
issueBoardsApp = new Vue({ issueBoardsApp = new Vue({
el: $boardApp, el: $boardApp,
......
...@@ -10,6 +10,7 @@ export default { ...@@ -10,6 +10,7 @@ export default {
GlSprintf, GlSprintf,
ModalCopyButton, ModalCopyButton,
}, },
inject: ['defaultBranchName'],
props: { props: {
modalId: { modalId: {
type: String, type: String,
...@@ -28,7 +29,11 @@ export default { ...@@ -28,7 +29,11 @@ export default {
modalInfo: { modalInfo: {
closeText: s__('EnableReviewApp|Close'), closeText: s__('EnableReviewApp|Close'),
copyToClipboardText: s__('EnableReviewApp|Copy snippet text'), copyToClipboardText: s__('EnableReviewApp|Copy snippet text'),
copyString: `deploy_review: title: s__('ReviewApp|Enable Review App'),
},
computed: {
modalInfoCopyStr() {
return `deploy_review:
stage: deploy stage: deploy
script: script:
- echo "Deploy a review app" - echo "Deploy a review app"
...@@ -38,8 +43,8 @@ export default { ...@@ -38,8 +43,8 @@ export default {
only: only:
- branches - branches
except: except:
- master`, - ${this.defaultBranchName}`;
title: s__('ReviewApp|Enable Review App'), },
}, },
}; };
</script> </script>
...@@ -75,7 +80,9 @@ export default { ...@@ -75,7 +80,9 @@ export default {
</gl-sprintf> </gl-sprintf>
</p> </p>
<div class="gl-display-flex align-items-start"> <div class="gl-display-flex align-items-start">
<pre class="gl-w-full"> {{ $options.modalInfo.copyString }} </pre> <pre class="gl-w-full" data-testid="enable-review-app-copy-string">
{{ modalInfoCopyStr }} </pre
>
<modal-copy-button <modal-copy-button
:title="$options.modalInfo.copyToClipboardText" :title="$options.modalInfo.copyToClipboardText"
:text="$options.modalInfo.copyString" :text="$options.modalInfo.copyString"
...@@ -90,7 +97,9 @@ export default { ...@@ -90,7 +97,9 @@ export default {
<strong>{{ content }}</strong> <strong>{{ content }}</strong>
</template> </template>
<template #link="{ content }"> <template #link="{ content }">
<gl-link href="blob/master/.gitlab-ci.yml" target="_blank">{{ content }}</gl-link> <gl-link :href="`blob/${defaultBranchName}/.gitlab-ci.yml`" target="_blank">{{
content
}}</gl-link>
</template> </template>
</gl-sprintf> </gl-sprintf>
</p> </p>
......
...@@ -22,6 +22,7 @@ export default () => { ...@@ -22,6 +22,7 @@ export default () => {
apolloProvider, apolloProvider,
provide: { provide: {
projectPath: el.dataset.projectPath, projectPath: el.dataset.projectPath,
defaultBranchName: el.dataset.defaultBranchName,
}, },
data() { data() {
const environmentsData = el.dataset; const environmentsData = el.dataset;
......
...@@ -314,7 +314,7 @@ export default { ...@@ -314,7 +314,7 @@ export default {
<h4>{{ s__('FeatureFlags|Strategies') }}</h4> <h4>{{ s__('FeatureFlags|Strategies') }}</h4>
<div class="flex align-items-baseline justify-content-between"> <div class="flex align-items-baseline justify-content-between">
<p class="mr-3">{{ $options.translations.newHelpText }}</p> <p class="mr-3">{{ $options.translations.newHelpText }}</p>
<gl-button variant="success" category="secondary" @click="addStrategy"> <gl-button variant="confirm" category="secondary" @click="addStrategy">
{{ s__('FeatureFlags|Add strategy') }} {{ s__('FeatureFlags|Add strategy') }}
</gl-button> </gl-button>
</div> </div>
...@@ -575,7 +575,7 @@ export default { ...@@ -575,7 +575,7 @@ export default {
ref="submitButton" ref="submitButton"
:disabled="readOnly" :disabled="readOnly"
type="button" type="button"
variant="success" variant="confirm"
class="js-ff-submit col-xs-12" class="js-ff-submit col-xs-12"
@click="handleSubmit" @click="handleSubmit"
>{{ submitText }}</gl-button >{{ submitText }}</gl-button
......
...@@ -23,7 +23,7 @@ export default { ...@@ -23,7 +23,7 @@ export default {
}, },
}, },
radioVals: { radioVals: {
/* Use the default branch (e.g. master) */ /* Use the default branch (e.g. main) */
DEFAULT: 'DEFAULT', DEFAULT: 'DEFAULT',
/* Create a new branch */ /* Create a new branch */
NEW: 'NEW', NEW: 'NEW',
......
...@@ -25,6 +25,9 @@ export default () => { ...@@ -25,6 +25,9 @@ export default () => {
return { return {
noteableData, noteableData,
endpoints: {
metadata: notesDataset.endpointMetadata,
},
currentUserData: JSON.parse(notesDataset.currentUserData), currentUserData: JSON.parse(notesDataset.currentUserData),
notesData: JSON.parse(notesDataset.notesData), notesData: JSON.parse(notesDataset.notesData),
helpPagePath: notesDataset.helpPagePath, helpPagePath: notesDataset.helpPagePath,
...@@ -54,6 +57,7 @@ export default () => { ...@@ -54,6 +57,7 @@ export default () => {
}, },
created() { created() {
this.setActiveTab(window.mrTabs.getCurrentAction()); this.setActiveTab(window.mrTabs.getCurrentAction());
this.setEndpoints(this.endpoints);
}, },
mounted() { mounted() {
this.notesCountBadge = $('.issuable-details').find('.notes-tab .badge'); this.notesCountBadge = $('.issuable-details').find('.notes-tab .badge');
...@@ -65,7 +69,7 @@ export default () => { ...@@ -65,7 +69,7 @@ export default () => {
window.mrTabs.eventHub.$off('MergeRequestTabChange', this.setActiveTab); window.mrTabs.eventHub.$off('MergeRequestTabChange', this.setActiveTab);
}, },
methods: { methods: {
...mapActions(['setActiveTab']), ...mapActions(['setActiveTab', 'setEndpoints']),
updateDiscussionTabCounter() { updateDiscussionTabCounter() {
this.notesCountBadge.text(this.discussionTabCounter); this.notesCountBadge.text(this.discussionTabCounter);
}, },
......
import types from './mutation_types'; import types from './mutation_types';
export default { export function setActiveTab({ commit }, tab) {
setActiveTab({ commit }, tab) {
commit(types.SET_ACTIVE_TAB, tab); commit(types.SET_ACTIVE_TAB, tab);
}, }
};
export function setEndpoints({ commit }, endpoints) {
commit(types.SET_ENDPOINTS, endpoints);
}
import actions from '../actions'; import * as actions from '../actions';
import getters from '../getters'; import getters from '../getters';
import mutations from '../mutations'; import mutations from '../mutations';
export default () => ({ export default () => ({
state: { state: {
endpoints: {},
activeTab: null, activeTab: null,
}, },
actions, actions,
......
export default { export default {
SET_ACTIVE_TAB: 'SET_ACTIVE_TAB', SET_ACTIVE_TAB: 'SET_ACTIVE_TAB',
SET_ENDPOINTS: 'SET_ENDPOINTS',
}; };
...@@ -4,4 +4,7 @@ export default { ...@@ -4,4 +4,7 @@ export default {
[types.SET_ACTIVE_TAB](state, tab) { [types.SET_ACTIVE_TAB](state, tab) {
Object.assign(state, { activeTab: tab }); Object.assign(state, { activeTab: tab });
}, },
[types.SET_ENDPOINTS](state, endpoints) {
Object.assign(state, { endpoints });
},
}; };
import initJobDetails from '~/jobs'; import initJobDetails from '~/jobs';
document.addEventListener('DOMContentLoaded', initJobDetails); initJobDetails();
...@@ -82,7 +82,7 @@ export default { ...@@ -82,7 +82,7 @@ export default {
</gl-alert> </gl-alert>
<div v-else> <div v-else>
<div class="gl-display-flex gl-align-items-center"> <div class="gl-display-flex gl-align-items-center">
<gl-icon :size="18" name="lock" use-deprecated-sizes class="gl-text-gray-500 gl-mr-3" /> <gl-icon :size="16" name="lock" class="gl-text-gray-500 gl-mr-3" />
{{ $options.i18n.viewOnlyMessage }} {{ $options.i18n.viewOnlyMessage }}
</div> </div>
<div class="gl-mt-3 gl-border-solid gl-border-gray-100 gl-border-1"> <div class="gl-mt-3 gl-border-solid gl-border-gray-100 gl-border-1">
......
<script> <script>
import { GlLink } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
export default { export default {
components: { components: {
GlLink, GlButton,
}, },
props: { props: {
currentPath: { currentPath: {
...@@ -32,15 +32,15 @@ export default { ...@@ -32,15 +32,15 @@ export default {
<h5 class="m-0 dropdown-bold-header">{{ __('Download this directory') }}</h5> <h5 class="m-0 dropdown-bold-header">{{ __('Download this directory') }}</h5>
<div class="dropdown-menu-content"> <div class="dropdown-menu-content">
<div class="btn-group ml-0 w-100"> <div class="btn-group ml-0 w-100">
<gl-link <gl-button
v-for="(link, index) in normalizedLinks" v-for="(link, index) in normalizedLinks"
:key="index" :key="index"
:href="link.path" :href="link.path"
:class="{ 'btn-primary': index === 0 }" :variant="index === 0 ? 'confirm' : 'default'"
class="btn btn-xs" size="small"
> >
{{ link.text }} {{ link.text }}
</gl-link> </gl-button>
</div> </div>
</div> </div>
</section> </section>
......
...@@ -40,8 +40,8 @@ ...@@ -40,8 +40,8 @@
[data-page$='epic_boards:index'], [data-page$='epic_boards:index'],
[data-page$='epic_boards:show'] { [data-page$='epic_boards:show'] {
.filter-form { .filtered-search-wrapper {
display: none; display: none !important;
} }
} }
......
...@@ -6,22 +6,11 @@ module Projects ...@@ -6,22 +6,11 @@ module Projects
include PackagesHelper include PackagesHelper
before_action :authorize_update_container_image!, only: [:destroy] before_action :authorize_update_container_image!, only: [:destroy]
before_action :ensure_root_container_repository!, only: [:index]
def index def index
respond_to do |format| respond_to do |format|
format.html format.html { ensure_root_container_repository! }
format.json do format.json { render_404 }
@images = ContainerRepositoriesFinder.new(user: current_user, subject: project, params: params.slice(:name))
.execute
track_package_event(:list_repositories, :container)
serializer = ContainerRepositoriesSerializer
.new(project: project, current_user: current_user)
render json: serializer.with_pagination(request, response).represent(@images)
end
end end
end end
......
...@@ -5,18 +5,37 @@ module Resolvers ...@@ -5,18 +5,37 @@ module Resolvers
class GroupMilestonesResolver < MilestonesResolver class GroupMilestonesResolver < MilestonesResolver
argument :include_descendants, GraphQL::BOOLEAN_TYPE, argument :include_descendants, GraphQL::BOOLEAN_TYPE,
required: false, required: false,
description: 'Also return milestones in all subgroups and subprojects.' description: 'Include milestones from all subgroups and subprojects.'
argument :include_ancestors, GraphQL::BOOLEAN_TYPE,
required: false,
description: 'Include milestones from all parent groups.'
type Types::MilestoneType.connection_type, null: true type Types::MilestoneType.connection_type, null: true
private private
def parent_id_parameters(args) def parent_id_parameters(args)
return { group_ids: parent.id } unless args[:include_descendants].present? include_ancestors = args[:include_ancestors].present?
include_descendants = args[:include_descendants].present?
return { group_ids: parent.id } unless include_ancestors || include_descendants
group_ids = if include_ancestors && include_descendants
parent.self_and_hierarchy
elsif include_ancestors
parent.self_and_ancestors
else
parent.self_and_descendants
end
project_ids = if include_descendants
group_projects.with_issues_or_mrs_available_for_user(current_user)
else
nil
end
{ {
group_ids: parent.self_and_descendants.public_or_visible_to_user(current_user).select(:id), group_ids: group_ids.public_or_visible_to_user(current_user).select(:id),
project_ids: group_projects.with_issues_or_mrs_available_for_user(current_user) project_ids: project_ids
} }
end end
......
...@@ -21,7 +21,13 @@ class Experiment < ApplicationRecord ...@@ -21,7 +21,13 @@ class Experiment < ApplicationRecord
# Create or update the recorded experiment_user row for the user in this experiment. # Create or update the recorded experiment_user row for the user in this experiment.
def record_user_and_group(user, group_type, context = {}) def record_user_and_group(user, group_type, context = {})
experiment_user = experiment_users.find_or_initialize_by(user: user) experiment_user = experiment_users.find_or_initialize_by(user: user)
experiment_user.update!(group_type: group_type, context: merged_context(experiment_user, context)) experiment_user.assign_attributes(group_type: group_type, context: merged_context(experiment_user, context))
# We only call save when necessary because this causes the request to stick to the primary DB
# even when the save is a no-op
# https://gitlab.com/gitlab-org/gitlab/-/issues/324649
experiment_user.save! if experiment_user.changed?
experiment_user
end end
def record_conversion_event_for_user(user, context = {}) def record_conversion_event_for_user(user, context = {})
...@@ -32,7 +38,14 @@ class Experiment < ApplicationRecord ...@@ -32,7 +38,14 @@ class Experiment < ApplicationRecord
end end
def record_group_and_variant!(group, variant) def record_group_and_variant!(group, variant)
experiment_subjects.find_or_initialize_by(group: group).update!(variant: variant) experiment_subject = experiment_subjects.find_or_initialize_by(group: group)
experiment_subject.assign_attributes(variant: variant)
# We only call save when necessary because this causes the request to stick to the primary DB
# even when the save is a no-op
# https://gitlab.com/gitlab-org/gitlab/-/issues/324649
experiment_subject.save! if experiment_subject.changed?
experiment_subject
end end
private private
......
...@@ -6,4 +6,5 @@ ...@@ -6,4 +6,5 @@
"can-create-environment" => can?(current_user, :create_environment, @project).to_s, "can-create-environment" => can?(current_user, :create_environment, @project).to_s,
"new-environment-path" => new_project_environment_path(@project), "new-environment-path" => new_project_environment_path(@project),
"help-page-path" => help_page_path("ci/environments/index.md"), "help-page-path" => help_page_path("ci/environments/index.md"),
"project-path" => @project.full_path } } "project-path" => @project.full_path,
"default-branch-name" => @project.default_branch_or_master } }
...@@ -13,6 +13,8 @@ ...@@ -13,6 +13,8 @@
- add_page_specific_style 'page_bundles/reports' - add_page_specific_style 'page_bundles/reports'
- add_page_specific_style 'page_bundles/ci_status' - add_page_specific_style 'page_bundles/ci_status'
- add_page_startup_api_call @endpoint_metadata_url
.merge-request{ data: { mr_action: mr_action, url: merge_request_path(@merge_request, format: :json), project_path: project_path(@merge_request.project), lock_version: @merge_request.lock_version } } .merge-request{ data: { mr_action: mr_action, url: merge_request_path(@merge_request, format: :json), project_path: project_path(@merge_request.project), lock_version: @merge_request.lock_version } }
= render "projects/merge_requests/mr_title" = render "projects/merge_requests/mr_title"
...@@ -63,6 +65,7 @@ ...@@ -63,6 +65,7 @@
- add_page_startup_api_call widget_project_json_merge_request_path(@project, @merge_request, format: :json) - add_page_startup_api_call widget_project_json_merge_request_path(@project, @merge_request, format: :json)
- add_page_startup_api_call cached_widget_project_json_merge_request_path(@project, @merge_request, format: :json) - add_page_startup_api_call cached_widget_project_json_merge_request_path(@project, @merge_request, format: :json)
#js-vue-mr-discussions{ data: { notes_data: notes_data(@merge_request, Feature.enabled?(:paginated_notes, @project)).to_json, #js-vue-mr-discussions{ data: { notes_data: notes_data(@merge_request, Feature.enabled?(:paginated_notes, @project)).to_json,
endpoint_metadata: @endpoint_metadata_url,
noteable_data: serialize_issuable(@merge_request, serializer: 'noteable'), noteable_data: serialize_issuable(@merge_request, serializer: 'noteable'),
noteable_type: 'MergeRequest', noteable_type: 'MergeRequest',
target_type: 'merge_request', target_type: 'merge_request',
...@@ -75,8 +78,6 @@ ...@@ -75,8 +78,6 @@
= render "projects/merge_requests/tabs/pane", name: "pipelines", id: "pipelines", class: "pipelines" do = render "projects/merge_requests/tabs/pane", name: "pipelines", id: "pipelines", class: "pipelines" do
- if number_of_pipelines.nonzero? - if number_of_pipelines.nonzero?
= render 'projects/commit/pipelines_list', disable_initialization: true, endpoint: pipelines_project_merge_request_path(@project, @merge_request) = render 'projects/commit/pipelines_list', disable_initialization: true, endpoint: pipelines_project_merge_request_path(@project, @merge_request)
- if mr_action === "diffs"
- add_page_startup_api_call @endpoint_metadata_url
- params = request.query_parameters - params = request.query_parameters
- if Feature.enabled?(:default_merge_ref_for_diffs, @project, default_enabled: :yaml) - if Feature.enabled?(:default_merge_ref_for_diffs, @project, default_enabled: :yaml)
- params = params.merge(diff_head: true) - params = params.merge(diff_head: true)
......
...@@ -5,7 +5,8 @@ ...@@ -5,7 +5,8 @@
- placeholder = local_assigns[:placeholder] || _('Search or filter results...') - placeholder = local_assigns[:placeholder] || _('Search or filter results...')
- is_not_boards_modal_or_productivity_analytics = type != :boards_modal && type != :productivity_analytics - is_not_boards_modal_or_productivity_analytics = type != :boards_modal && type != :productivity_analytics
- block_css_class = is_not_boards_modal_or_productivity_analytics ? 'row-content-block second-block' : '' - block_css_class = is_not_boards_modal_or_productivity_analytics ? 'row-content-block second-block' : ''
- if board && board.to_type == "EpicBoard" - is_epic_board = board&.to_type == "EpicBoard"
- if is_epic_board
- user_can_admin_list = can?(current_user, :admin_epic_board_list, board.resource_parent) - user_can_admin_list = can?(current_user, :admin_epic_board_list, board.resource_parent)
- elsif board - elsif board
- user_can_admin_list = can?(current_user, :admin_issue_board_list, board.resource_parent) - user_can_admin_list = can?(current_user, :admin_issue_board_list, board.resource_parent)
...@@ -21,7 +22,7 @@ ...@@ -21,7 +22,7 @@
- if @can_bulk_update - if @can_bulk_update
.check-all-holder.d-none.d-sm-block.hidden .check-all-holder.d-none.d-sm-block.hidden
= check_box_tag "check-all-issues", nil, false, class: "check-all-issues left" = check_box_tag "check-all-issues", nil, false, class: "check-all-issues left"
- if Feature.enabled?(:boards_filtered_search, @group) - if Feature.enabled?(:boards_filtered_search, @group) && is_epic_board
#js-board-filtered-search #js-board-filtered-search
- else - else
.issues-other-filters.filtered-search-wrapper.d-flex.flex-column.flex-md-row .issues-other-filters.filtered-search-wrapper.d-flex.flex-column.flex-md-row
......
---
title: Support include_ancestors when querying group milestones via GraphQL
merge_request: 56667
author:
type: added
---
title: Update master to main inside monitor copy
merge_request: 56264
author:
type: changed
---
title: Prevent sticking to DB primary when experiments are tracked
merge_request: 56852
author:
type: performance
---
title: Move to btn-confirm in download directory dropdown
merge_request: 56193
author: Yogi (@yo)
type: changed
---
title: Move to confirm variant from success in feature_flags directory
merge_request: 56202
author: Yogi (@yo)
type: changed
---
title: Change icon size in the pipeline editor
merge_request: 56780
author:
type: changed
---
title: Enabled phabricator importer by default
merge_request: 56765
author:
type: added
---
title: Remove JSON endpoint for project container index
merge_request: 52407
author: Takuya Noguchi
type: other
...@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/groups/gitlab-org/-/epics/1197 ...@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/groups/gitlab-org/-/epics/1197
milestone: '12.0' milestone: '12.0'
type: development type: development
group: group::import group: group::import
default_enabled: false default_enabled: true
...@@ -56,10 +56,14 @@ The instance then notifies the user. ...@@ -56,10 +56,14 @@ The instance then notifies the user.
## Review existing GPG keys ## Review existing GPG keys
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/282429) in GitLab 13.10. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/282429) in GitLab 13.10.
> - It's [deployed behind a feature flag](../feature_flags.md), disabled by default. > - It was [deployed behind a feature flag](../feature_flags.md), disabled by default.
> - It's disabled on GitLab.com. > - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/292961) on GitLab 13.11.
> - It's not recommended for production use. > - It's enabled on GitLab.com.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-the-gpg-keys-view). > - It's recommended for production use.
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-the-gpg-keys-view).
WARNING:
This feature might not be available to you. Check the **version history** note above for details.
You can view all existing GPG in your GitLab instance by navigating to the You can view all existing GPG in your GitLab instance by navigating to the
credentials inventory GPG Keys tab, as well as the following properties: credentials inventory GPG Keys tab, as well as the following properties:
...@@ -72,10 +76,10 @@ credentials inventory GPG Keys tab, as well as the following properties: ...@@ -72,10 +76,10 @@ credentials inventory GPG Keys tab, as well as the following properties:
### Enable or disable the GPG keys view ### Enable or disable the GPG keys view
Enabling or disabling the GPG keys view is under development and not ready for production use. It is Enabling or disabling the GPG keys view is under development but ready for production use.
deployed behind a feature flag that is **disabled by default**. It is deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md) [GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
can enable it. can opt to disable it.
To enable it: To enable it:
......
...@@ -183,10 +183,11 @@ The following variables are used for configuring specific analyzers (used for a ...@@ -183,10 +183,11 @@ The following variables are used for configuring specific analyzers (used for a
| `BUNDLER_AUDIT_ADVISORY_DB_URL` | `bundler-audit` | `https://github.com/rubysec/ruby-advisory-db` | URL of the advisory database used by bundler-audit. | | `BUNDLER_AUDIT_ADVISORY_DB_URL` | `bundler-audit` | `https://github.com/rubysec/ruby-advisory-db` | URL of the advisory database used by bundler-audit. |
| `BUNDLER_AUDIT_ADVISORY_DB_REF_NAME` | `bundler-audit` | `master` | Git ref for the advisory database specified by `BUNDLER_AUDIT_ADVISORY_DB_URL`. | | `BUNDLER_AUDIT_ADVISORY_DB_REF_NAME` | `bundler-audit` | `master` | Git ref for the advisory database specified by `BUNDLER_AUDIT_ADVISORY_DB_URL`. |
| `GEMNASIUM_DB_LOCAL_PATH` | `gemnasium` | `/gemnasium-db` | Path to local Gemnasium database. | | `GEMNASIUM_DB_LOCAL_PATH` | `gemnasium` | `/gemnasium-db` | Path to local Gemnasium database. |
| `GEMNASIUM_DB_UPDATE_DISABLED` | `gemnasium` | `"false"` | Disable automatic updates for the `gemnasium-db` advisory database (For usage see: [examples](#hosting-a-copy-of-the-gemnasium_db-advisory-database))|
| `GEMNASIUM_DB_REMOTE_URL` | `gemnasium` | `https://gitlab.com/gitlab-org/security-products/gemnasium-db.git` | Repository URL for fetching the Gemnasium database. | | `GEMNASIUM_DB_REMOTE_URL` | `gemnasium` | `https://gitlab.com/gitlab-org/security-products/gemnasium-db.git` | Repository URL for fetching the Gemnasium database. |
| `GEMNASIUM_DB_REF_NAME` | `gemnasium` | `master` | Branch name for remote repository database. `GEMNASIUM_DB_REMOTE_URL` is required. | | `GEMNASIUM_DB_REF_NAME` | `gemnasium` | `master` | Branch name for remote repository database. `GEMNASIUM_DB_REMOTE_URL` is required. |
| `DS_REMEDIATE` | `gemnasium` | `"true"` | Enable automatic remediation of vulnerable dependencies. | | `DS_REMEDIATE` | `gemnasium` | `"true"` | Enable automatic remediation of vulnerable dependencies. |
| `DS_JAVA_VERSION` | `gemnasium-maven` | `11` | Version of Java. Available versions: `8`, `11`, `13`, `14`. Maven and Gradle use the Java version specified by this value. | | `DS_JAVA_VERSION` | `gemnasium-maven` | `11` | Version of Java. Available versions: `8`, `11`, `13`, `14`, `15`. Maven and Gradle use the Java version specified by this value. |
| `MAVEN_CLI_OPTS` | `gemnasium-maven` | `"-DskipTests --batch-mode"` | List of command line arguments that are passed to `maven` by the analyzer. See an example for [using private repositories](../index.md#using-private-maven-repositories). | | `MAVEN_CLI_OPTS` | `gemnasium-maven` | `"-DskipTests --batch-mode"` | List of command line arguments that are passed to `maven` by the analyzer. See an example for [using private repositories](../index.md#using-private-maven-repositories). |
| `GRADLE_CLI_OPTS` | `gemnasium-maven` | | List of command line arguments that are passed to `gradle` by the analyzer. | | `GRADLE_CLI_OPTS` | `gemnasium-maven` | | List of command line arguments that are passed to `gradle` by the analyzer. |
| `SBT_CLI_OPTS` | `gemnasium-maven` | | List of command-line arguments that the analyzer passes to `sbt`. | | `SBT_CLI_OPTS` | `gemnasium-maven` | | List of command-line arguments that the analyzer passes to `sbt`. |
...@@ -505,6 +506,50 @@ ensure that it can reach your private repository. Here is an example configurati ...@@ -505,6 +506,50 @@ ensure that it can reach your private repository. Here is an example configurati
setuptools.ssl_support.cert_paths = ['internal.crt'] setuptools.ssl_support.cert_paths = ['internal.crt']
``` ```
## Hosting a copy of the gemnasium_db advisory database
The [gemnasium_db](https://gitlab.com/gitlab-org/security-products/gemnasium-db) Git repository is
used by `gemnasium`, `gemnasium-maven`, and `gemnasium-python` as the source of vulnerability data.
This repository updates at scan time to fetch the latest advisories. However, due to a restricted
networking environment, running this update is sometimes not possible. In this case, a user can do
one of the following:
- [Host a copy of the advisory database](#host-a-copy-of-the-advisory-database)
- [Use a local clone](#use-a-local-clone)
### Host a copy of the advisory database
If [gemnasium-db](https://gitlab.com/gitlab-org/security-products/gemnasium-db) is not reachable
from within the environment, the user can host their own Git copy. Then the analyzer can be
instructed to update the database from the user's copy by using `GEMNASIUM_DB_REMOTE_URL`:
```yaml
variables:
GEMNASIUM_DB_REMOTE_URL: https://users-own-copy.example.com/gemnasium-db/.git
...
```
### Use a local clone
If a hosted copy is not possible, then the user can clone [gemnasium-db](https://gitlab.com/gitlab-org/security-products/gemnasium-db)
or create an archive before the scan and point the analyzer to the directory (using:
`GEMNASIUM_DB_LOCAL_PATH`). Turn off the analyzer's self-update mechanism (using:
`GEMNASIUM_DB_UPDATE_DISABLED`). In this example, the database directory is created in the
`before_script`, before the `gemnasium` analyzer's scan job:
```yaml
...
gemnasium-dependency_scanning:
variables:
GEMNASIUM_DB_LOCAL_PATH: ./gemnasium-db-local
GEMNASIUM_DB_UPDATE_DISABLED: "true"
before_script:
- mkdir $GEMNASIUM_DB_LOCAL_PATH
- tar -xzf gemnasium_db.tar.gz -C $GEMNASIUM_DB_LOCAL_PATH
```
## Limitations ## Limitations
### Referencing local dependencies using a path in JavaScript projects ### Referencing local dependencies using a path in JavaScript projects
......
...@@ -36,13 +36,4 @@ of the project being imported into, then the user will be linked. ...@@ -36,13 +36,4 @@ of the project being imported into, then the user will be linked.
## Enabling this feature ## Enabling this feature
While this feature is incomplete, a feature flag is required to enable it so that Enable Phabricator as an [import source](../../admin_area/settings/visibility_and_access_controls.md#import-sources) in the Admin Area.
we can gain early feedback before releasing it for everyone. To enable it:
1. Run the following command in a Rails console:
```ruby
Feature.enable(:phabricator_import)
```
1. Enable Phabricator as an [import source](../../admin_area/settings/visibility_and_access_controls.md#import-sources) in the Admin Area.
<script>
import { mapActions } from 'vuex';
import { historyPushState } from '~/lib/utils/common_utils';
import { setUrlParams } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
export default {
i18n: {
search: __('Search'),
},
components: { FilteredSearch },
inject: ['search'],
computed: {
initialSearch() {
return [{ type: 'filtered-search-term', value: { data: this.search } }];
},
},
methods: {
...mapActions(['performSearch']),
tokens() {
return [];
},
handleSearch(filters = []) {
const [item] = filters;
const search = item?.value?.data || '';
historyPushState(setUrlParams({ search }));
this.performSearch();
},
},
};
</script>
<template>
<filtered-search
class="gl-w-full"
namespace=""
:tokens="tokens()"
:search-input-placeholder="$options.i18n.search"
:initial-filter-value="initialSearch"
@onFilter="handleSearch"
/>
</template>
import Vue from 'vue';
import EpicFilteredSearch from 'ee_component/boards/components/epic_filtered_search.vue';
import store from '~/boards/stores';
import { queryToObject } from '~/lib/utils/url_utility';
export default () => {
const queryParams = queryToObject(window.location.search);
const el = document.getElementById('js-board-filtered-search');
if (!el) {
return null;
}
return new Vue({
el,
provide: {
search: queryParams?.search || '',
},
store, // TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/324094
apolloProvider: {},
render: (createElement) => createElement(EpicFilteredSearch),
});
};
...@@ -39,6 +39,14 @@ export default () => { ...@@ -39,6 +39,14 @@ export default () => {
} }
}); });
if (gon?.features?.boardsFilteredSearch) {
import('ee/boards/epic_filtered_search')
.then(({ default: initFilteredSearch }) => {
initFilteredSearch();
})
.catch(() => {});
}
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Vue({ new Vue({
el: $boardApp, el: $boardApp,
......
...@@ -22,7 +22,7 @@ class Admin::CredentialsController < Admin::ApplicationController ...@@ -22,7 +22,7 @@ class Admin::CredentialsController < Admin::ApplicationController
end end
def check_gpg_keys_list_enabled! def check_gpg_keys_list_enabled!
render_404 if show_gpg_keys? && Feature.disabled?(:credential_inventory_gpg_keys) render_404 if show_gpg_keys? && Feature.disabled?(:credential_inventory_gpg_keys, default_enabled: :yaml)
end end
override :credentials_inventory_path override :credentials_inventory_path
...@@ -57,7 +57,7 @@ class Admin::CredentialsController < Admin::ApplicationController ...@@ -57,7 +57,7 @@ class Admin::CredentialsController < Admin::ApplicationController
override :gpg_keys_available? override :gpg_keys_available?
def gpg_keys_available? def gpg_keys_available?
Feature.enabled?(:credential_inventory_gpg_keys) Feature.enabled?(:credential_inventory_gpg_keys, default_enabled: :yaml)
end end
override :users override :users
......
...@@ -9,6 +9,7 @@ class Groups::EpicBoardsController < Groups::ApplicationController ...@@ -9,6 +9,7 @@ class Groups::EpicBoardsController < Groups::ApplicationController
before_action :assign_endpoint_vars before_action :assign_endpoint_vars
before_action do before_action do
push_frontend_feature_flag(:epic_boards, group, default_enabled: :yaml) push_frontend_feature_flag(:epic_boards, group, default_enabled: :yaml)
push_frontend_feature_flag(:boards_filtered_search, group)
end end
feature_category :boards feature_category :boards
......
---
title: Set credential_inventory_gpg_keys feature-flag to enabled by default
merge_request: 56764
author:
type: changed
...@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/292961 ...@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/292961
milestone: '13.8' milestone: '13.8'
type: development type: development
group: group::compliance group: group::compliance
default_enabled: false default_enabled: true
import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import EpicFilteredSearch from 'ee_component/boards/components/epic_filtered_search.vue';
import { createStore } from '~/boards/stores';
import * as commonUtils from '~/lib/utils/common_utils';
import FilteredSearchBarRoot from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('EpicFilteredSearch', () => {
let wrapper;
let store;
const createComponent = () => {
wrapper = shallowMount(EpicFilteredSearch, {
localVue,
provide: { search: '' },
store,
});
};
const findFilteredSearch = () => wrapper.findComponent(FilteredSearchBarRoot);
beforeEach(() => {
// this needed for actions call for performSearch
window.gon = { features: {} };
});
afterEach(() => {
wrapper.destroy();
});
describe('default', () => {
beforeEach(() => {
store = createStore();
jest.spyOn(store, 'dispatch');
createComponent();
});
it('renders FilteredSearch', () => {
expect(findFilteredSearch().exists()).toBe(true);
});
describe('when onFilter is emitted', () => {
it('calls performSearch', () => {
findFilteredSearch().vm.$emit('onFilter', [{ value: { data: '' } }]);
expect(store.dispatch).toHaveBeenCalledWith('performSearch');
});
it('calls historyPushState', () => {
jest.spyOn(commonUtils, 'historyPushState');
findFilteredSearch().vm.$emit('onFilter', [{ value: { data: 'searchQuery' } }]);
expect(commonUtils.historyPushState).toHaveBeenCalledWith(
'http://test.host/?search=searchQuery',
);
});
});
});
});
...@@ -5,7 +5,7 @@ module Gitlab ...@@ -5,7 +5,7 @@ module Gitlab
BaseError = Class.new(StandardError) BaseError = Class.new(StandardError)
def self.available? def self.available?
Feature.enabled?(:phabricator_import) && Feature.enabled?(:phabricator_import, default_enabled: :yaml) &&
Gitlab::CurrentSettings.import_sources.include?('phabricator') Gitlab::CurrentSettings.import_sources.include?('phabricator')
end end
end end
......
...@@ -16,19 +16,19 @@ RSpec.describe Projects::Registry::RepositoriesController do ...@@ -16,19 +16,19 @@ RSpec.describe Projects::Registry::RepositoriesController do
project.add_developer(user) project.add_developer(user)
end end
shared_examples 'with name parameter' do shared_examples 'renders 200 for html and 404 for json' do
let_it_be(:repo) { create(:container_repository, project: project, name: 'my_searched_image') } it 'successfully renders container repositories', :snowplow do
let_it_be(:another_repo) { create(:container_repository, project: project, name: 'bar') } go_to_index
it 'returns the searched repo' do
go_to_index(format: :json, params: { name: 'my_searched_image' })
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response.length).to eq 1 # event tracked in GraphQL API: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44926
expect(json_response.first).to include( expect_no_snowplow_event
'id' => repo.id, end
'name' => repo.name
) it 'returns 404 for request in json format' do
go_to_index(format: :json)
expect(response).to have_gitlab_http_status(:not_found)
end end
end end
...@@ -50,33 +50,12 @@ RSpec.describe Projects::Registry::RepositoriesController do ...@@ -50,33 +50,12 @@ RSpec.describe Projects::Registry::RepositoriesController do
tags: %w[rc1 latest]) tags: %w[rc1 latest])
end end
it 'successfully renders container repositories', :snowplow do
go_to_index
expect_no_snowplow_event
expect(response).to have_gitlab_http_status(:ok)
end
it 'tracks the event', :snowplow do
go_to_index(format: :json)
expect_snowplow_event(category: anything, action: 'list_repositories')
end
it 'creates a root container repository' do it 'creates a root container repository' do
expect { go_to_index }.to change { ContainerRepository.all.count }.by(1) expect { go_to_index }.to change { ContainerRepository.all.count }.by(1)
expect(ContainerRepository.first).to be_root_repository expect(ContainerRepository.first).to be_root_repository
end end
it 'json has a list of projects' do it_behaves_like 'renders 200 for html and 404 for json'
go_to_index(format: :json)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('registry/repositories')
expect(response).to include_pagination_headers
end
it_behaves_like 'with name parameter'
end end
context 'when there are no tags for this repository' do context 'when there are no tags for this repository' do
...@@ -84,22 +63,11 @@ RSpec.describe Projects::Registry::RepositoriesController do ...@@ -84,22 +63,11 @@ RSpec.describe Projects::Registry::RepositoriesController do
stub_container_registry_tags(repository: :any, tags: []) stub_container_registry_tags(repository: :any, tags: [])
end end
it 'successfully renders container repositories' do
go_to_index
expect(response).to have_gitlab_http_status(:ok)
end
it 'does not ensure root container repository' do it 'does not ensure root container repository' do
expect { go_to_index }.not_to change { ContainerRepository.all.count } expect { go_to_index }.not_to change { ContainerRepository.all.count }
end end
it 'responds with json if asked' do it_behaves_like 'renders 200 for html and 404 for json'
go_to_index(format: :json)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_kind_of(Array)
end
end end
end end
end end
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import EnableReviewAppButton from '~/environments/components/enable_review_app_modal.vue'; import EnableReviewAppButton from '~/environments/components/enable_review_app_modal.vue';
import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue'; import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
...@@ -11,15 +12,25 @@ describe('Enable Review App Button', () => { ...@@ -11,15 +12,25 @@ describe('Enable Review App Button', () => {
describe('renders the modal', () => { describe('renders the modal', () => {
beforeEach(() => { beforeEach(() => {
wrapper = shallowMount(EnableReviewAppButton, { wrapper = extendedWrapper(
shallowMount(EnableReviewAppButton, {
propsData: { propsData: {
modalId: 'fake-id', modalId: 'fake-id',
}, },
provide: {
defaultBranchName: 'main',
},
}),
);
}); });
it('renders the defaultBranchName copy', () => {
const findCopyString = () => wrapper.findByTestId('enable-review-app-copy-string');
expect(findCopyString().text()).toContain('- main');
}); });
it('renders the copyToClipboard button', () => { it('renders the copyToClipboard button', () => {
expect(wrapper.find(ModalCopyButton).exists()).toBe(true); expect(wrapper.findComponent(ModalCopyButton).exists()).toBe(true);
}); });
}); });
}); });
import testAction from 'helpers/vuex_action_helper';
import { setEndpoints } from '~/mr_notes/stores/actions';
import mutationTypes from '~/mr_notes/stores/mutation_types';
describe('MR Notes Mutator Actions', () => {
describe('setEndpoints', () => {
it('should trigger the SET_ENDPOINTS state mutation', (done) => {
const endpoints = { endpointA: 'a' };
testAction(
setEndpoints,
endpoints,
{},
[
{
type: mutationTypes.SET_ENDPOINTS,
payload: endpoints,
},
],
[],
done,
);
});
});
});
import mutationTypes from '~/mr_notes/stores/mutation_types';
import mutations from '~/mr_notes/stores/mutations';
describe('MR Notes Mutations', () => {
describe(mutationTypes.SET_ENDPOINTS, () => {
it('should set the endpoints value', () => {
const state = {};
const endpoints = { endpointA: 'A', endpointB: 'B' };
mutations[mutationTypes.SET_ENDPOINTS](state, endpoints);
expect(state.endpoints).toEqual(endpoints);
});
});
});
...@@ -16,22 +16,30 @@ exports[`Repository directory download links component renders downloads links f ...@@ -16,22 +16,30 @@ exports[`Repository directory download links component renders downloads links f
<div <div
class="btn-group ml-0 w-100" class="btn-group ml-0 w-100"
> >
<gl-link-stub <gl-button-stub
class="btn btn-xs btn-primary" buttontextclasses=""
category="primary"
href="http://test.com/?path=app" href="http://test.com/?path=app"
icon=""
size="small"
variant="confirm"
> >
zip zip
</gl-link-stub> </gl-button-stub>
<gl-link-stub <gl-button-stub
class="btn btn-xs" buttontextclasses=""
category="primary"
href="http://test.com/?path=app" href="http://test.com/?path=app"
icon=""
size="small"
variant="default"
> >
tar tar
</gl-link-stub> </gl-button-stub>
</div> </div>
</div> </div>
</section> </section>
...@@ -53,22 +61,30 @@ exports[`Repository directory download links component renders downloads links f ...@@ -53,22 +61,30 @@ exports[`Repository directory download links component renders downloads links f
<div <div
class="btn-group ml-0 w-100" class="btn-group ml-0 w-100"
> >
<gl-link-stub <gl-button-stub
class="btn btn-xs btn-primary" buttontextclasses=""
category="primary"
href="http://test.com/?path=app/assets" href="http://test.com/?path=app/assets"
icon=""
size="small"
variant="confirm"
> >
zip zip
</gl-link-stub> </gl-button-stub>
<gl-link-stub <gl-button-stub
class="btn btn-xs" buttontextclasses=""
category="primary"
href="http://test.com/?path=app/assets" href="http://test.com/?path=app/assets"
icon=""
size="small"
variant="default"
> >
tar tar
</gl-link-stub> </gl-button-stub>
</div> </div>
</div> </div>
</section> </section>
......
...@@ -136,5 +136,56 @@ RSpec.describe Resolvers::GroupMilestonesResolver do ...@@ -136,5 +136,56 @@ RSpec.describe Resolvers::GroupMilestonesResolver do
expect(resolve_group_milestones(args)).to match_array([milestone1, milestone2, milestone3]) expect(resolve_group_milestones(args)).to match_array([milestone1, milestone2, milestone3])
end end
end end
describe 'include_descendants and include_ancestors' do
let_it_be(:parent_group) { create(:group, :public) }
let_it_be(:group) { create(:group, :public, parent: parent_group) }
let_it_be(:accessible_group) { create(:group, :private, parent: group) }
let_it_be(:accessible_project) { create(:project, group: accessible_group) }
let_it_be(:inaccessible_group) { create(:group, :private, parent: group) }
let_it_be(:inaccessible_project) { create(:project, :private, group: group) }
let_it_be(:milestone1) { create(:milestone, group: group) }
let_it_be(:milestone2) { create(:milestone, group: accessible_group) }
let_it_be(:milestone3) { create(:milestone, project: accessible_project) }
let_it_be(:milestone4) { create(:milestone, group: inaccessible_group) }
let_it_be(:milestone5) { create(:milestone, project: inaccessible_project) }
let_it_be(:milestone6) { create(:milestone, group: parent_group) }
before do
accessible_group.add_developer(current_user)
end
context 'when including neither ancestor or descendant milestones in a public group' do
let(:args) { {} }
it 'finds milestones only in accessible projects and groups' do
expect(resolve_group_milestones(args)).to match_array([milestone1])
end
end
context 'when including descendant milestones in a public group' do
let(:args) { { include_descendants: true } }
it 'finds milestones only in accessible projects and groups' do
expect(resolve_group_milestones(args)).to match_array([milestone1, milestone2, milestone3])
end
end
context 'when including ancestor milestones in a public group' do
let(:args) { { include_ancestors: true } }
it 'finds milestones only in accessible projects and groups' do
expect(resolve_group_milestones(args)).to match_array([milestone1, milestone6])
end
end
context 'when including both ancestor or descendant milestones in a public group' do
let(:args) { { include_descendants: true, include_ancestors: true } }
it 'finds milestones only in accessible projects and groups' do
expect(resolve_group_milestones(args)).to match_array([milestone1, milestone2, milestone3, milestone6])
end
end
end
end end
end end
...@@ -244,18 +244,27 @@ RSpec.describe Experiment do ...@@ -244,18 +244,27 @@ RSpec.describe Experiment do
context 'when no existing experiment_subject record exists for the given group' do context 'when no existing experiment_subject record exists for the given group' do
it 'creates an experiment_subject record' do it 'creates an experiment_subject record' do
expect_next(ExperimentSubject).to receive(:update!).with(variant: variant).and_call_original
expect { record_group_and_variant! }.to change(ExperimentSubject, :count).by(1) expect { record_group_and_variant! }.to change(ExperimentSubject, :count).by(1)
expect(ExperimentSubject.last.variant).to eq(variant.to_s)
end end
end end
context 'when an existing experiment_subject exists for the given group' do context 'when an existing experiment_subject exists for the given group' do
context 'but it belonged to a different variant' do let_it_be(:experiment_subject) do
let!(:experiment_subject) do
create(:experiment_subject, experiment: experiment, group: group, user: nil, variant: :experimental) create(:experiment_subject, experiment: experiment, group: group, user: nil, variant: :experimental)
end end
context 'when it belongs to the same variant' do
let(:variant) { :experimental }
it 'does not initiate a transaction' do
expect(ActiveRecord::Base.connection).not_to receive(:transaction)
subject
end
end
context 'but it belonged to a different variant' do
it 'updates the variant value' do it 'updates the variant value' do
expect { record_group_and_variant! }.to change { experiment_subject.reload.variant }.to('control') expect { record_group_and_variant! }.to change { experiment_subject.reload.variant }.to('control')
end end
...@@ -299,6 +308,16 @@ RSpec.describe Experiment do ...@@ -299,6 +308,16 @@ RSpec.describe Experiment do
expect { subject }.not_to change(ExperimentUser, :count) expect { subject }.not_to change(ExperimentUser, :count)
end end
context 'when group type or context did not change' do
let(:context) { {} }
it 'does not initiate a transaction' do
expect(ActiveRecord::Base.connection).not_to receive(:transaction)
subject
end
end
context 'but the group_type and context has changed' do context 'but the group_type and context has changed' do
let(:group) { :experimental } let(:group) { :experimental }
......
...@@ -9,12 +9,14 @@ RSpec.describe 'Milestones through GroupQuery' do ...@@ -9,12 +9,14 @@ RSpec.describe 'Milestones through GroupQuery' do
let_it_be(:now) { Time.now } let_it_be(:now) { Time.now }
describe 'Get list of milestones from a group' do describe 'Get list of milestones from a group' do
let_it_be(:group) { create(:group) } let_it_be(:parent_group) { create(:group) }
let_it_be(:group) { create(:group, parent: parent_group) }
let_it_be(:milestone_1) { create(:milestone, group: group) } let_it_be(:milestone_1) { create(:milestone, group: group) }
let_it_be(:milestone_2) { create(:milestone, group: group, state: :closed, start_date: now, due_date: now + 1.day) } let_it_be(:milestone_2) { create(:milestone, group: group, state: :closed, start_date: now, due_date: now + 1.day) }
let_it_be(:milestone_3) { create(:milestone, group: group, start_date: now, due_date: now + 2.days) } let_it_be(:milestone_3) { create(:milestone, group: group, start_date: now, due_date: now + 2.days) }
let_it_be(:milestone_4) { create(:milestone, group: group, state: :closed, start_date: now - 2.days, due_date: now - 1.day) } let_it_be(:milestone_4) { create(:milestone, group: group, state: :closed, start_date: now - 2.days, due_date: now - 1.day) }
let_it_be(:milestone_from_other_group) { create(:milestone, group: create(:group)) } let_it_be(:milestone_from_other_group) { create(:milestone, group: create(:group)) }
let_it_be(:parent_milestone) { create(:milestone, group: parent_group) }
let(:milestone_data) { graphql_data['group']['milestones']['edges'] } let(:milestone_data) { graphql_data['group']['milestones']['edges'] }
...@@ -64,6 +66,9 @@ RSpec.describe 'Milestones through GroupQuery' do ...@@ -64,6 +66,9 @@ RSpec.describe 'Milestones through GroupQuery' do
accessible_group.add_developer(user) accessible_group.add_developer(user)
end end
context 'when including decendants' do
let(:args) { { include_descendants: true } }
it 'returns milestones also from subgroups and subprojects visible to user' do it 'returns milestones also from subgroups and subprojects visible to user' do
fetch_milestones(user, args) fetch_milestones(user, args)
...@@ -75,6 +80,21 @@ RSpec.describe 'Milestones through GroupQuery' do ...@@ -75,6 +80,21 @@ RSpec.describe 'Milestones through GroupQuery' do
end end
end end
context 'when including ancestors' do
let(:args) { { include_ancestors: true } }
it 'returns milestones from ancestor groups' do
fetch_milestones(user, args)
expect_array_response(
milestone_1.to_global_id.to_s, milestone_2.to_global_id.to_s,
milestone_3.to_global_id.to_s, milestone_4.to_global_id.to_s,
parent_milestone.to_global_id.to_s
)
end
end
end
def fetch_milestones(user = nil, args = {}) def fetch_milestones(user = nil, args = {})
post_graphql(milestones_query(args), current_user: user) post_graphql(milestones_query(args), current_user: user)
end end
......
...@@ -104,9 +104,9 @@ RSpec.configure do |config| ...@@ -104,9 +104,9 @@ RSpec.configure do |config|
warn "=== uptime" warn "=== uptime"
warn `uptime` warn `uptime`
warn "=== Prometheus metrics:" warn "=== Prometheus metrics:"
warn `curl -s http://localhost:9236/metrics` warn `curl -s -o log/gitaly-metrics.log http://localhost:9236/metrics`
warn "=== Taking goroutine dump in log/goroutines.log..." warn "=== Taking goroutine dump in log/goroutines.log..."
warn `curl -o log/goroutines.log http://localhost:9236/debug/pprof/goroutine?debug=2` warn `curl -s -o log/goroutines.log http://localhost:9236/debug/pprof/goroutine?debug=2`
end end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment