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 () => {
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
issueBoardsApp = new Vue({
el: $boardApp,
......
......@@ -10,6 +10,7 @@ export default {
GlSprintf,
ModalCopyButton,
},
inject: ['defaultBranchName'],
props: {
modalId: {
type: String,
......@@ -28,7 +29,11 @@ export default {
modalInfo: {
closeText: s__('EnableReviewApp|Close'),
copyToClipboardText: s__('EnableReviewApp|Copy snippet text'),
copyString: `deploy_review:
title: s__('ReviewApp|Enable Review App'),
},
computed: {
modalInfoCopyStr() {
return `deploy_review:
stage: deploy
script:
- echo "Deploy a review app"
......@@ -38,8 +43,8 @@ export default {
only:
- branches
except:
- master`,
title: s__('ReviewApp|Enable Review App'),
- ${this.defaultBranchName}`;
},
},
};
</script>
......@@ -75,7 +80,9 @@ export default {
</gl-sprintf>
</p>
<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
:title="$options.modalInfo.copyToClipboardText"
:text="$options.modalInfo.copyString"
......@@ -90,7 +97,9 @@ export default {
<strong>{{ content }}</strong>
</template>
<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>
</gl-sprintf>
</p>
......
......@@ -22,6 +22,7 @@ export default () => {
apolloProvider,
provide: {
projectPath: el.dataset.projectPath,
defaultBranchName: el.dataset.defaultBranchName,
},
data() {
const environmentsData = el.dataset;
......
......@@ -314,7 +314,7 @@ export default {
<h4>{{ s__('FeatureFlags|Strategies') }}</h4>
<div class="flex align-items-baseline justify-content-between">
<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') }}
</gl-button>
</div>
......@@ -575,7 +575,7 @@ export default {
ref="submitButton"
:disabled="readOnly"
type="button"
variant="success"
variant="confirm"
class="js-ff-submit col-xs-12"
@click="handleSubmit"
>{{ submitText }}</gl-button
......
......@@ -23,7 +23,7 @@ export default {
},
},
radioVals: {
/* Use the default branch (e.g. master) */
/* Use the default branch (e.g. main) */
DEFAULT: 'DEFAULT',
/* Create a new branch */
NEW: 'NEW',
......
......@@ -25,6 +25,9 @@ export default () => {
return {
noteableData,
endpoints: {
metadata: notesDataset.endpointMetadata,
},
currentUserData: JSON.parse(notesDataset.currentUserData),
notesData: JSON.parse(notesDataset.notesData),
helpPagePath: notesDataset.helpPagePath,
......@@ -54,6 +57,7 @@ export default () => {
},
created() {
this.setActiveTab(window.mrTabs.getCurrentAction());
this.setEndpoints(this.endpoints);
},
mounted() {
this.notesCountBadge = $('.issuable-details').find('.notes-tab .badge');
......@@ -65,7 +69,7 @@ export default () => {
window.mrTabs.eventHub.$off('MergeRequestTabChange', this.setActiveTab);
},
methods: {
...mapActions(['setActiveTab']),
...mapActions(['setActiveTab', 'setEndpoints']),
updateDiscussionTabCounter() {
this.notesCountBadge.text(this.discussionTabCounter);
},
......
import types from './mutation_types';
export default {
setActiveTab({ commit }, tab) {
export function setActiveTab({ commit }, 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 mutations from '../mutations';
export default () => ({
state: {
endpoints: {},
activeTab: null,
},
actions,
......
export default {
SET_ACTIVE_TAB: 'SET_ACTIVE_TAB',
SET_ENDPOINTS: 'SET_ENDPOINTS',
};
......@@ -4,4 +4,7 @@ export default {
[types.SET_ACTIVE_TAB](state, tab) {
Object.assign(state, { activeTab: tab });
},
[types.SET_ENDPOINTS](state, endpoints) {
Object.assign(state, { endpoints });
},
};
import initJobDetails from '~/jobs';
document.addEventListener('DOMContentLoaded', initJobDetails);
initJobDetails();
......@@ -82,7 +82,7 @@ export default {
</gl-alert>
<div v-else>
<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 }}
</div>
<div class="gl-mt-3 gl-border-solid gl-border-gray-100 gl-border-1">
......
<script>
import { GlLink } from '@gitlab/ui';
import { GlButton } from '@gitlab/ui';
export default {
components: {
GlLink,
GlButton,
},
props: {
currentPath: {
......@@ -32,15 +32,15 @@ export default {
<h5 class="m-0 dropdown-bold-header">{{ __('Download this directory') }}</h5>
<div class="dropdown-menu-content">
<div class="btn-group ml-0 w-100">
<gl-link
<gl-button
v-for="(link, index) in normalizedLinks"
:key="index"
:href="link.path"
:class="{ 'btn-primary': index === 0 }"
class="btn btn-xs"
:variant="index === 0 ? 'confirm' : 'default'"
size="small"
>
{{ link.text }}
</gl-link>
</gl-button>
</div>
</div>
</section>
......
......@@ -40,8 +40,8 @@
[data-page$='epic_boards:index'],
[data-page$='epic_boards:show'] {
.filter-form {
display: none;
.filtered-search-wrapper {
display: none !important;
}
}
......
......@@ -6,22 +6,11 @@ module Projects
include PackagesHelper
before_action :authorize_update_container_image!, only: [:destroy]
before_action :ensure_root_container_repository!, only: [:index]
def index
respond_to do |format|
format.html
format.json do
@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
format.html { ensure_root_container_repository! }
format.json { render_404 }
end
end
......
......@@ -5,18 +5,37 @@ module Resolvers
class GroupMilestonesResolver < MilestonesResolver
argument :include_descendants, GraphQL::BOOLEAN_TYPE,
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
private
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),
project_ids: group_projects.with_issues_or_mrs_available_for_user(current_user)
group_ids: group_ids.public_or_visible_to_user(current_user).select(:id),
project_ids: project_ids
}
end
......
......@@ -21,7 +21,13 @@ class Experiment < ApplicationRecord
# Create or update the recorded experiment_user row for the user in this experiment.
def record_user_and_group(user, group_type, context = {})
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
def record_conversion_event_for_user(user, context = {})
......@@ -32,7 +38,14 @@ class Experiment < ApplicationRecord
end
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
private
......
......@@ -6,4 +6,5 @@
"can-create-environment" => can?(current_user, :create_environment, @project).to_s,
"new-environment-path" => new_project_environment_path(@project),
"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 @@
- add_page_specific_style 'page_bundles/reports'
- 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 } }
= render "projects/merge_requests/mr_title"
......@@ -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 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,
endpoint_metadata: @endpoint_metadata_url,
noteable_data: serialize_issuable(@merge_request, serializer: 'noteable'),
noteable_type: 'MergeRequest',
target_type: 'merge_request',
......@@ -75,8 +78,6 @@
= render "projects/merge_requests/tabs/pane", name: "pipelines", id: "pipelines", class: "pipelines" do
- if number_of_pipelines.nonzero?
= 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
- if Feature.enabled?(:default_merge_ref_for_diffs, @project, default_enabled: :yaml)
- params = params.merge(diff_head: true)
......
......@@ -5,7 +5,8 @@
- placeholder = local_assigns[:placeholder] || _('Search or filter results...')
- 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' : ''
- 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)
- elsif board
- user_can_admin_list = can?(current_user, :admin_issue_board_list, board.resource_parent)
......@@ -21,7 +22,7 @@
- if @can_bulk_update
.check-all-holder.d-none.d-sm-block.hidden
= 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
- else
.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
milestone: '12.0'
type: development
group: group::import
default_enabled: false
default_enabled: true
......@@ -56,10 +56,14 @@ The instance then notifies the user.
## Review existing GPG keys
> - [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's disabled on GitLab.com.
> - It's not recommended for production use.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-the-gpg-keys-view).
> - It was [deployed behind a feature flag](../feature_flags.md), disabled by default.
> - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/292961) on GitLab 13.11.
> - It's enabled on GitLab.com.
> - 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
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
Enabling or disabling the GPG keys view is under development and not ready for production use. It is
deployed behind a feature flag that is **disabled by default**.
Enabling or disabling the GPG keys view is under development but ready for production use.
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)
can enable it.
can opt to disable it.
To enable it:
......
......@@ -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_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_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_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_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). |
| `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`. |
......@@ -505,6 +506,50 @@ ensure that it can reach your private repository. Here is an example configurati
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
### 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.
## Enabling this feature
While this feature is incomplete, a feature flag is required to enable it so that
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.
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 () => {
}
});
if (gon?.features?.boardsFilteredSearch) {
import('ee/boards/epic_filtered_search')
.then(({ default: initFilteredSearch }) => {
initFilteredSearch();
})
.catch(() => {});
}
// eslint-disable-next-line no-new
new Vue({
el: $boardApp,
......
......@@ -22,7 +22,7 @@ class Admin::CredentialsController < Admin::ApplicationController
end
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
override :credentials_inventory_path
......@@ -57,7 +57,7 @@ class Admin::CredentialsController < Admin::ApplicationController
override :gpg_keys_available?
def gpg_keys_available?
Feature.enabled?(:credential_inventory_gpg_keys)
Feature.enabled?(:credential_inventory_gpg_keys, default_enabled: :yaml)
end
override :users
......
......@@ -9,6 +9,7 @@ class Groups::EpicBoardsController < Groups::ApplicationController
before_action :assign_endpoint_vars
before_action do
push_frontend_feature_flag(:epic_boards, group, default_enabled: :yaml)
push_frontend_feature_flag(:boards_filtered_search, group)
end
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
milestone: '13.8'
type: development
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
BaseError = Class.new(StandardError)
def self.available?
Feature.enabled?(:phabricator_import) &&
Feature.enabled?(:phabricator_import, default_enabled: :yaml) &&
Gitlab::CurrentSettings.import_sources.include?('phabricator')
end
end
......
......@@ -16,19 +16,19 @@ RSpec.describe Projects::Registry::RepositoriesController do
project.add_developer(user)
end
shared_examples 'with name parameter' do
let_it_be(:repo) { create(:container_repository, project: project, name: 'my_searched_image') }
let_it_be(:another_repo) { create(:container_repository, project: project, name: 'bar') }
it 'returns the searched repo' do
go_to_index(format: :json, params: { name: 'my_searched_image' })
shared_examples 'renders 200 for html and 404 for json' do
it 'successfully renders container repositories', :snowplow do
go_to_index
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.length).to eq 1
expect(json_response.first).to include(
'id' => repo.id,
'name' => repo.name
)
# event tracked in GraphQL API: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44926
expect_no_snowplow_event
end
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
......@@ -50,33 +50,12 @@ RSpec.describe Projects::Registry::RepositoriesController do
tags: %w[rc1 latest])
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
expect { go_to_index }.to change { ContainerRepository.all.count }.by(1)
expect(ContainerRepository.first).to be_root_repository
end
it 'json has a list of projects' do
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'
it_behaves_like 'renders 200 for html and 404 for json'
end
context 'when there are no tags for this repository' do
......@@ -84,22 +63,11 @@ RSpec.describe Projects::Registry::RepositoriesController do
stub_container_registry_tags(repository: :any, tags: [])
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
expect { go_to_index }.not_to change { ContainerRepository.all.count }
end
it 'responds with json if asked' do
go_to_index(format: :json)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_kind_of(Array)
end
it_behaves_like 'renders 200 for html and 404 for json'
end
end
end
......
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 ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
......@@ -11,15 +12,25 @@ describe('Enable Review App Button', () => {
describe('renders the modal', () => {
beforeEach(() => {
wrapper = shallowMount(EnableReviewAppButton, {
wrapper = extendedWrapper(
shallowMount(EnableReviewAppButton, {
propsData: {
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', () => {
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
<div
class="btn-group ml-0 w-100"
>
<gl-link-stub
class="btn btn-xs btn-primary"
<gl-button-stub
buttontextclasses=""
category="primary"
href="http://test.com/?path=app"
icon=""
size="small"
variant="confirm"
>
zip
</gl-link-stub>
<gl-link-stub
class="btn btn-xs"
</gl-button-stub>
<gl-button-stub
buttontextclasses=""
category="primary"
href="http://test.com/?path=app"
icon=""
size="small"
variant="default"
>
tar
</gl-link-stub>
</gl-button-stub>
</div>
</div>
</section>
......@@ -53,22 +61,30 @@ exports[`Repository directory download links component renders downloads links f
<div
class="btn-group ml-0 w-100"
>
<gl-link-stub
class="btn btn-xs btn-primary"
<gl-button-stub
buttontextclasses=""
category="primary"
href="http://test.com/?path=app/assets"
icon=""
size="small"
variant="confirm"
>
zip
</gl-link-stub>
<gl-link-stub
class="btn btn-xs"
</gl-button-stub>
<gl-button-stub
buttontextclasses=""
category="primary"
href="http://test.com/?path=app/assets"
icon=""
size="small"
variant="default"
>
tar
</gl-link-stub>
</gl-button-stub>
</div>
</div>
</section>
......
......@@ -136,5 +136,56 @@ RSpec.describe Resolvers::GroupMilestonesResolver do
expect(resolve_group_milestones(args)).to match_array([milestone1, milestone2, milestone3])
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
......@@ -244,18 +244,27 @@ RSpec.describe Experiment do
context 'when no existing experiment_subject record exists for the given group' 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(ExperimentSubject.last.variant).to eq(variant.to_s)
end
end
context 'when an existing experiment_subject exists for the given group' do
context 'but it belonged to a different variant' do
let!(:experiment_subject) do
let_it_be(:experiment_subject) do
create(:experiment_subject, experiment: experiment, group: group, user: nil, variant: :experimental)
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
expect { record_group_and_variant! }.to change { experiment_subject.reload.variant }.to('control')
end
......@@ -299,6 +308,16 @@ RSpec.describe Experiment do
expect { subject }.not_to change(ExperimentUser, :count)
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
let(:group) { :experimental }
......
......@@ -9,12 +9,14 @@ RSpec.describe 'Milestones through GroupQuery' do
let_it_be(:now) { Time.now }
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_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_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(:parent_milestone) { create(:milestone, group: parent_group) }
let(:milestone_data) { graphql_data['group']['milestones']['edges'] }
......@@ -64,6 +66,9 @@ RSpec.describe 'Milestones through GroupQuery' do
accessible_group.add_developer(user)
end
context 'when including decendants' do
let(:args) { { include_descendants: true } }
it 'returns milestones also from subgroups and subprojects visible to user' do
fetch_milestones(user, args)
......@@ -75,6 +80,21 @@ RSpec.describe 'Milestones through GroupQuery' do
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 = {})
post_graphql(milestones_query(args), current_user: user)
end
......
......@@ -104,9 +104,9 @@ RSpec.configure do |config|
warn "=== uptime"
warn `uptime`
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 `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
......
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