Commit cff478eb authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 008921cd 18d2c779
d5777441938369d512a7825c57ccf6115d7afdfa
21e7c85471a8d7401fad69be300eaff1c0384577
<script>
import MetricCard from '~/analytics/shared/components/metric_card.vue';
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import { GlSingleStat } from '@gitlab/ui/dist/charts';
import createFlash from '~/flash';
import { number } from '~/lib/utils/unit_format';
import { s__ } from '~/locale';
......@@ -10,7 +11,8 @@ const defaultPrecision = 0;
export default {
name: 'UsageCounts',
components: {
MetricCard,
GlSkeletonLoading,
GlSingleStat,
},
data() {
return {
......@@ -56,10 +58,24 @@ export default {
</script>
<template>
<metric-card
:title="__('Usage Trends')"
:metrics="counts"
:is-loading="$apollo.queries.counts.loading"
class="gl-mt-4"
/>
<div>
<h2>
{{ __('Usage Trends') }}
</h2>
<div
class="gl-display-flex gl-flex-direction-column gl-md-flex-direction-row gl-my-6 gl-align-items-flex-start"
>
<gl-skeleton-loading v-if="$apollo.queries.counts.loading" />
<template v-else>
<gl-single-stat
v-for="count in counts"
:key="count.key"
class="gl-pr-9 gl-my-4 gl-md-mt-0 gl-md-mb-0"
:value="`${count.value}`"
:title="count.label"
:should-animate="true"
/>
</template>
</div>
</div>
</template>
......@@ -12,10 +12,8 @@ import BoardColumnDeprecated from './board_column_deprecated.vue';
export default {
components: {
BoardAddNewColumn,
BoardColumn:
gon.features?.graphqlBoardLists || gon.features?.epicBoards
? BoardColumn
: BoardColumnDeprecated,
BoardColumn,
BoardColumnDeprecated,
BoardContentSidebar: () => import('~/boards/components/board_content_sidebar.vue'),
EpicBoardContentSidebar: () =>
import('ee_component/boards/components/epic_board_content_sidebar.vue'),
......@@ -38,11 +36,14 @@ export default {
computed: {
...mapState(['boardLists', 'error', 'addColumnForm']),
...mapGetters(['isSwimlanesOn', 'isEpicBoard']),
useNewBoardColumnComponent() {
return this.glFeatures.graphqlBoardLists || this.isSwimlanesOn || this.isEpicBoard;
},
addColumnFormVisible() {
return this.addColumnForm?.visible;
},
boardListsToUse() {
return this.glFeatures.graphqlBoardLists || this.isSwimlanesOn || this.isEpicBoard
return this.useNewBoardColumnComponent
? sortBy([...Object.values(this.boardLists)], 'position')
: this.lists;
},
......@@ -65,6 +66,9 @@ export default {
return this.canDragColumns ? options : {};
},
boardColumnComponent() {
return this.useNewBoardColumnComponent ? BoardColumn : BoardColumnDeprecated;
},
},
methods: {
...mapActions(['moveList', 'unsetError']),
......@@ -102,7 +106,8 @@ export default {
class="boards-list gl-w-full gl-py-5 gl-px-3 gl-white-space-nowrap"
@end="handleDragOnEnd"
>
<board-column
<component
:is="boardColumnComponent"
v-for="(list, index) in boardListsToUse"
:key="index"
ref="board"
......
......@@ -279,9 +279,12 @@ class Namespace < ApplicationRecord
# Includes projects from this namespace and projects from all subgroups
# that belongs to this namespace
def all_projects
namespace = user? ? self : self_and_descendant_ids
Project.where(namespace: namespace)
if Feature.enabled?(:recursive_approach_for_all_projects, default_enabled: :yaml)
namespace = user? ? self : self_and_descendants
Project.where(namespace: namespace)
else
Project.inside_path(full_path)
end
end
def has_parent?
......
---
name: epic_boards
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48893
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/290039
milestone: '13.7'
name: recursive_approach_for_all_projects
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64632
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/334817
milestone: '14.1'
type: development
group: group::product planning
group: group::fulfillment
default_enabled: true
......@@ -20,7 +20,6 @@ On GitLab.com, we use [separate Redis
instances](../administration/redis/replication_and_failover.md#running-multiple-redis-clusters).
See the [Redis SRE guide](https://gitlab.com/gitlab-com/runbooks/-/blob/master/docs/redis/redis-survival-guide-for-sres.md)
for more details on our setup.
We do not currently use [ActionCable on GitLab.com](https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/228).
Every application process is configured to use the same Redis servers, so they
can be used for inter-process communication in cases where [PostgreSQL](sql.md)
......
......@@ -49,9 +49,10 @@ You can [disable comments](#disable-comments-on-jira-issues) on issues.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/280766) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.12 behind a feature flag, disabled by default.
> - [Deployed behind a feature flag](../../user/feature_flags.md), disabled by default.
> - Disabled on GitLab.com.
> - Not recommended for production use.
> - To use in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-the-ability-to-require-an-associated-jira-issue-on-merge-requests). **(ULTIMATE SELF)**
> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61722) in GitLab 14.1.
> - Enabled on GitLab.com.
> - Recommended for production use.
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-the-ability-to-require-an-associated-jira-issue-on-merge-requests). **(ULTIMATE SELF)**
This in-development feature might not be available for your use. There can be
[risks when enabling features still in development](../../user/application_security/index.md#security-approvals-in-merge-requests).
......@@ -188,9 +189,9 @@ adding a comment to the Jira issue:
## Enable or disable the ability to require an associated Jira issue on merge requests
The ability to require an associated Jira issue on merge requests is under development
and not ready for production use. It is deployed behind a feature flag that is
**disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md) can enable it.
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 opt to disable it.
To enable it:
......
......@@ -21,10 +21,11 @@ consumption of your instance. Keep this in mind when adjusting the maximum.
To speed the loading time of merge request views and branch comparison views
on your instance, you can configure three instance-level maximum values for diffs:
- **Maximum diff patch size**: The total size, in bytes, of the entire diff.
- **Maximum diff files**: The total number of files changed in a diff.
- **Maximum diff files**: The total number of files changed in a diff. The default value is 1000.
- **Maximum diff lines**: The total number of lines changed in a diff. The default value is 50,000.
| Value | Definition | Default value | Maximum value |
| ----- | ---------- | :-----------: | :-----------: |
| **Maximum diff patch size** | The total size, in bytes, of the entire diff. | 200 KB | 500 KB |
| **Maximum diff files** | The total number of files changed in a diff. | 1000 | 3000 |
| **Maximum diff lines** | The total number of lines changed in a diff. | 50,000 | 100,000 |
When a diff reaches 10% of any of these values, the files are shown in a
collapsed view, with a link to expand the diff. Diffs that exceed any of the
......@@ -35,7 +36,7 @@ To configure these values:
1. On the top bar, select **Menu >** **{admin}** **Admin**.
1. In the left sidebar, select **Settings > General**.
1. Expand **Diff limits**.
1. Enter a value for **Maximum diff patch size**, measured in bytes.
1. Enter a value for the diff limit.
1. Select **Save changes**.
<!-- ## Troubleshooting
......
......@@ -7,17 +7,16 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Epic Boards **(PREMIUM)**
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5067) in GitLab 13.10.
> - [Deployed behind a feature flag](../../feature_flags.md), disabled by default.
> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/290039) in GitLab 14.0.
> - Enabled on GitLab.com.
> - Recommended for production use.
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](../../../administration/feature_flags.md).
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/290039) in GitLab 14.1.
Epic boards build on the existing [epic tracking functionality](index.md) and
[labels](../../project/labels.md). Your epics appear as cards in vertical lists, organized by their assigned
labels.
To view an epic board, in a group, select **Epics > Boards**.
To view an epic board:
1. On the top bar, select **Menu > Groups** and find your group.
1. On the left sidebar, select **Epics > Boards**.
![GitLab epic board - Premium](img/epic_board_v14_0.png)
......@@ -29,7 +28,8 @@ Prerequisites:
To create a new epic board:
1. Go to your group and select **Epics > Boards**.
1. On the top bar, select **Menu > Groups** and find your group.
1. On the left sidebar, select **Epics > Boards**.
1. In the upper left corner, select the dropdown with the current board name.
1. Select **Create new board**.
1. Enter the new board's title.
......@@ -77,7 +77,8 @@ Prerequisites:
To create a new list:
1. Go to your group and select **Epics > Boards**.
1. On the top bar, select **Menu > Groups** and find your group.
1. On the left sidebar, select **Epics > Boards**.
1. In the upper-right corner, select **Create list**.
1. In the **New list** column expand the **Select a label** dropdown and select the label to use as
list scope.
......@@ -161,22 +162,3 @@ To edit the scope of an epic board:
- Show or hide the Open and Closed columns.
- Select other labels as the board's scope.
1. Select **Save changes**.
## Enable or disable epic boards
Epic boards are 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 disable it.
To disable it:
```ruby
Feature.disable(:epic_boards)
```
To enable it:
```ruby
Feature.enable(:epic_boards)
```
<script>
import { GlTable, GlEmptyState, GlButton, GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
import {
GlTable,
GlEmptyState,
GlButton,
GlAlert,
GlSprintf,
GlLink,
GlIcon,
GlTooltipDirective,
} from '@gitlab/ui';
import { mapState, mapGetters } from 'vuex';
import { PREDEFINED_NETWORK_POLICIES } from 'ee/threat_monitoring/constants';
import createFlash from '~/flash';
import { getTimeago } from '~/lib/utils/datetime_utility';
import { setUrlFragment, mergeUrlParams } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
import { __, s__ } from '~/locale';
import networkPoliciesQuery from '../graphql/queries/network_policies.query.graphql';
import scanExecutionPoliciesQuery from '../graphql/queries/scan_execution_policies.query.graphql';
import EnvironmentPicker from './environment_picker.vue';
......@@ -29,9 +38,13 @@ export default {
GlAlert,
GlSprintf,
GlLink,
GlIcon,
EnvironmentPicker,
PolicyDrawer,
},
directives: {
GlTooltip: GlTooltipDirective,
},
inject: ['projectPath'],
props: {
documentationPath: {
......@@ -130,22 +143,26 @@ export default {
label: s__('NetworkPolicies|Namespace'),
};
const fields = [
{
key: 'status',
label: '',
thClass: 'gl-w-3',
tdAttr: {
'data-testid': 'policy-status-cell',
},
},
{
key: 'name',
label: s__('NetworkPolicies|Name'),
thClass: 'gl-w-half',
},
{
key: 'status',
label: s__('NetworkPolicies|Status'),
},
{
key: 'updatedAt',
label: s__('NetworkPolicies|Last modified'),
},
];
// Adds column 'namespace' only while 'all environments' option is selected
if (this.allEnvironments) fields.splice(1, 0, namespace);
if (this.allEnvironments) fields.splice(2, 0, namespace);
return fields;
},
......@@ -170,12 +187,16 @@ export default {
bTable.clearSelected();
},
},
emptyStateDescription: s__(
`NetworkPolicies|Policies are a specification of how groups of pods are allowed to communicate with each other's network endpoints.`,
),
autodevopsNoticeDescription: s__(
`NetworkPolicies|If you are using Auto DevOps, your %{monospacedStart}auto-deploy-values.yaml%{monospacedEnd} file will not be updated if you change a policy in this section. Auto DevOps users should make changes by following the %{linkStart}Container Network Policy documentation%{linkEnd}.`,
),
i18n: {
emptyStateDescription: s__(
`NetworkPolicies|Policies are a specification of how groups of pods are allowed to communicate with each other's network endpoints.`,
),
autodevopsNoticeDescription: s__(
`NetworkPolicies|If you are using Auto DevOps, your %{monospacedStart}auto-deploy-values.yaml%{monospacedEnd} file will not be updated if you change a policy in this section. Auto DevOps users should make changes by following the %{linkStart}Container Network Policy documentation%{linkEnd}.`,
),
statusEnabled: __('Enabled'),
statusDisabled: __('Disabled'),
},
};
</script>
......@@ -188,7 +209,7 @@ export default {
:dismissible="false"
class="gl-mb-3"
>
<gl-sprintf :message="$options.autodevopsNoticeDescription">
<gl-sprintf :message="$options.i18n.autodevopsNoticeDescription">
<template #monospaced="{ content }">
<span class="gl-font-monospace">{{ content }}</span>
</template>
......@@ -230,7 +251,14 @@ export default {
@row-selected="presentPolicyDrawer"
>
<template #cell(status)="value">
{{ value.item.enabled ? __('Enabled') : __('Disabled') }}
<gl-icon
v-if="value.item.enabled"
v-gl-tooltip="$options.i18n.statusEnabled"
:aria-label="$options.i18n.statusEnabled"
name="check-circle-filled"
class="gl-text-green-700"
/>
<span v-else class="gl-sr-only">{{ $options.i18n.statusDisabled }}</span>
</template>
<template #cell(updatedAt)="value">
......@@ -242,7 +270,7 @@ export default {
<gl-empty-state
ref="tableEmptyState"
:title="s__('NetworkPolicies|No policies detected')"
:description="$options.emptyStateDescription"
:description="$options.i18n.emptyStateDescription"
:primary-button-link="documentationFullPath"
:primary-button-text="__('Learn more')"
/>
......
......@@ -8,9 +8,6 @@ class Groups::EpicBoardsController < Groups::ApplicationController
before_action :redirect_to_recent_board, only: [:index]
before_action :assign_endpoint_vars
before_action do
push_frontend_feature_flag(:epic_boards, group, default_enabled: :yaml)
end
track_redis_hll_event :index, :show, name: 'g_project_management_users_viewing_epic_boards'
......@@ -70,6 +67,6 @@ class Groups::EpicBoardsController < Groups::ApplicationController
end
def authorize_read_board!
access_denied! unless Feature.enabled?(:epic_boards, group, default_enabled: :yaml) && can?(current_user, :read_epic_board, group)
access_denied! unless can?(current_user, :read_epic_board, group)
end
end
......@@ -50,10 +50,6 @@ module Mutations
board = authorized_find!(id: args[:board_id])
epic = authorized_find!(id: args[:epic_id])
unless Feature.enabled?(:epic_boards, board.resource_parent, default_enabled: :yaml)
raise Gitlab::Graphql::Errors::ResourceNotAvailable, 'epic_boards feature is disabled'
end
move_epic(board, epic, move_list_arguments(args).merge(board_id: board.id))
{
......
......@@ -24,10 +24,6 @@ module Mutations
def resolve(**args)
board = authorized_find!(id: args[:id])
unless Feature.enabled?(:epic_boards, board.resource_parent, default_enabled: :yaml)
raise Gitlab::Graphql::Errors::ResourceNotAvailable, 'epic_boards feature is disabled'
end
::Boards::EpicBoards::UpdateService.new(board.resource_parent, current_user, args).execute(board)
{
......
......@@ -16,7 +16,6 @@ module Resolvers
alias_method :group, :object
def resolve(id: nil)
return unless Feature.enabled?(:epic_boards, group, default_enabled: :yaml)
return unless group.licensed_feature_available?(:epics)
authorize!
......
......@@ -8,7 +8,7 @@ module Boards
override :can_create_board?
def can_create_board?
Feature.enabled?(:epic_boards, parent, default_enabled: :yaml)
true
end
override :parent_board_collection
......
......@@ -7,10 +7,6 @@ module Boards
override :execute
def execute(board)
unless Feature.enabled?(:epic_boards, board.group, default_enabled: :yaml)
return ServiceResponse.error(message: 'Epic boards feature is not enabled.')
end
super
end
end
......
......@@ -11,10 +11,6 @@ module Boards
return ServiceResponse.error(message: 'Epics feature is not available.')
end
unless Feature.enabled?(:epic_boards, list.board.group, default_enabled: :yaml)
return ServiceResponse.error(message: 'Epic boards feature is not enabled.')
end
unless can?(current_user, :admin_epic_board_list, list)
return ServiceResponse.error(message: 'The epic board list that you are attempting to destroy does not '\
'exist or you don\'t have permission to perform this action')
......
......@@ -42,7 +42,7 @@ module Boards
end
def available?
group.licensed_feature_available?(:epics) && Feature.enabled?(:epic_boards, parent, default_enabled: :yaml)
group.licensed_feature_available?(:epics)
end
def allowed?
......
......@@ -20,10 +20,9 @@
= link_to group_epics_path(group), title: 'List' do
%span= _('List')
- if Feature.enabled?(:epic_boards, group, default_enabled: :yaml)
= nav_link(path: ['epic_boards#index', 'epic_boards#show'], html_options: { class: "home" }) do
= link_to group_epic_boards_path(group), title: 'Boards' do
%span= _('Boards')
= nav_link(path: ['epic_boards#index', 'epic_boards#show'], html_options: { class: "home" }) do
= link_to group_epic_boards_path(group), title: 'Boards' do
%span= _('Boards')
= nav_link(path: 'roadmap#show', html_options: { class: 'home' }) do
= link_to group_roadmap_path(group), title: 'Roadmap' do
......
......@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/300271
milestone: '13.9'
type: development
group: group::compliance
default_enabled: false
default_enabled: true
......@@ -80,28 +80,6 @@ RSpec.describe 'Group navbar' do
end
context 'when epics are available' do
before do
stub_licensed_features(epics: true)
stub_feature_flags(epic_boards: false)
insert_after_nav_item(
_('Group information'),
new_nav_item: {
nav_item: _('Epics'),
nav_sub_items: [
_('List'),
_('Roadmap')
]
}
)
visit group_path(group)
end
it_behaves_like 'verified navigation bar'
end
context 'when epics and epic boards are available' do
before do
stub_licensed_features(epics: true)
......
import { GlSkeletonLoader } from '@gitlab/ui';
import { GlSingleStat } from '@gitlab/ui/dist/charts';
import { shallowMount } from '@vue/test-utils';
import ThroughputStats from 'ee/analytics/merge_request_analytics/components/throughput_stats';
import ThroughputStats from 'ee/analytics/merge_request_analytics/components/throughput_stats.vue';
import { stats } from '../mock_data';
describe('ThroughputStats', () => {
......
import { GlToggle } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import ToggleLabels from 'ee/boards/components/toggle_labels';
import ToggleLabels from 'ee/boards/components/toggle_labels.vue';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
const localVue = createLocalVue();
......
import { shallowMount } from '@vue/test-utils';
import App from 'ee/on_demand_scans/components/profile_selector/scanner_profile_summary';
import App from 'ee/on_demand_scans/components/profile_selector/scanner_profile_summary.vue';
import { scannerProfiles } from 'ee_jest/on_demand_scans/mocks/mock_data';
const [profile] = scannerProfiles;
......
import { shallowMount } from '@vue/test-utils';
import App from 'ee/on_demand_scans/components/profile_selector/site_profile_summary';
import App from 'ee/on_demand_scans/components/profile_selector/site_profile_summary.vue';
import { siteProfiles } from 'ee_jest/on_demand_scans/mocks/mock_data';
describe('DastSiteProfileSummary', () => {
......
......@@ -5,8 +5,8 @@ import AddEditScheduleForm from 'ee/oncall_schedules/components/add_edit_schedul
import AddEditScheduleModal, {
i18n,
} from 'ee/oncall_schedules/components/add_edit_schedule_modal.vue';
import { editScheduleModalId } from 'ee/oncall_schedules/components/oncall_schedule';
import { addScheduleModalId } from 'ee/oncall_schedules/components/oncall_schedules_wrapper';
import { editScheduleModalId } from 'ee/oncall_schedules/components/oncall_schedule.vue';
import { addScheduleModalId } from 'ee/oncall_schedules/components/oncall_schedules_wrapper.vue';
import updateOncallScheduleMutation from 'ee/oncall_schedules/graphql/mutations/update_oncall_schedule.mutation.graphql';
import getOncallSchedulesWithRotationsQuery from 'ee/oncall_schedules/graphql/queries/get_oncall_schedules.query.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
......
......@@ -4,7 +4,7 @@ import VueApollo from 'vue-apollo';
import DeleteScheduleModal, {
i18n,
} from 'ee/oncall_schedules/components/delete_schedule_modal.vue';
import { deleteScheduleModalId } from 'ee/oncall_schedules/components/oncall_schedule';
import { deleteScheduleModalId } from 'ee/oncall_schedules/components/oncall_schedule.vue';
import destroyOncallScheduleMutation from 'ee/oncall_schedules/graphql/mutations/destroy_oncall_schedule.mutation.graphql';
import getOncallSchedulesQuery from 'ee/oncall_schedules/graphql/queries/get_oncall_schedules.query.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
......
......@@ -77,6 +77,7 @@ describe('PolicyList component', () => {
const findEnvironmentsPicker = () => wrapper.find({ ref: 'environmentsPicker' });
const findPoliciesTable = () => wrapper.findComponent(GlTable);
const findPolicyStatusCells = () => wrapper.findAllByTestId('policy-status-cell');
const findPolicyDrawer = () => wrapper.findByTestId('policyDrawer');
const findAutodevopsAlert = () => wrapper.findByTestId('autodevopsAlert');
......@@ -159,14 +160,36 @@ describe('PolicyList component', () => {
);
});
describe('status column', () => {
beforeEach(() => {
mountWrapper();
});
it('renders a checkmark icon for enabled policies', () => {
const icon = findPolicyStatusCells().at(0).find('svg');
expect(icon.exists()).toBe(true);
expect(icon.props('name')).toBe('check-circle-filled');
expect(icon.props('ariaLabel')).toBe('Enabled');
});
it('renders a "Disabled" label for screen readers for disabled policies', () => {
const span = findPolicyStatusCells().at(1).find('span');
expect(span.exists()).toBe(true);
expect(span.attributes('class')).toBe('gl-sr-only');
expect(span.text()).toBe('Disabled');
});
});
describe('with allEnvironments enabled', () => {
beforeEach(() => {
mountWrapper();
wrapper.vm.$store.state.threatMonitoring.allEnvironments = true;
});
it('renders policies table', () => {
const namespaceHeader = findPoliciesTable().findAll('[role="columnheader"]').at(1);
it('renders namespace column', () => {
const namespaceHeader = findPoliciesTable().findAll('[role="columnheader"]').at(2);
expect(namespaceHeader.text()).toBe('Namespace');
});
});
......
......@@ -27,10 +27,9 @@ RSpec.describe ::Mutations::Boards::EpicBoards::Create do
it { is_expected.to have_graphql_fields(:epic_board).at_least }
end
context 'with epic feature enabled and epic_boards feature flag enabled' do
context 'with epic feature enabled' do
before do
stub_licensed_features(epics: true)
stub_feature_flags(epic_boards: true)
end
context 'when user does not have permission to create epic board' do
......@@ -52,14 +51,6 @@ RSpec.describe ::Mutations::Boards::EpicBoards::Create do
end
end
context 'with epic_boards feature flag disabled' do
before do
stub_feature_flags(epic_boards: false)
end
it_behaves_like 'epic board creation error'
end
context 'with epic feature disabled' do
before do
stub_licensed_features(epics: false)
......
......@@ -42,7 +42,6 @@ RSpec.describe ::Mutations::Boards::EpicBoards::EpicMoveList do
describe '#resolve' do
before do
stub_licensed_features(epics: true)
stub_feature_flags(epic_boards: true)
end
context 'when user does not have permissions' do
......
......@@ -27,10 +27,9 @@ RSpec.describe ::Mutations::Boards::EpicBoards::Update do
it { is_expected.to have_graphql_fields(:epic_board).at_least }
end
context 'with epic feature enabled and epic_boards feature flag enabled' do
context 'with epic feature enabled' do
before do
stub_licensed_features(epics: true)
stub_feature_flags(epic_boards: true)
end
context 'when user does not have permission to update epic board' do
......@@ -50,14 +49,6 @@ RSpec.describe ::Mutations::Boards::EpicBoards::Update do
expect(result[:epic_board].hide_backlog_list).to be_truthy
end
end
context 'with epic_boards feature flag disabled' do
before do
stub_feature_flags(epic_boards: false)
end
it_behaves_like 'epic board update error'
end
end
describe '#ready?' do
......
......@@ -18,7 +18,6 @@ RSpec.describe Mutations::Boards::Epics::Create do
before do
stub_licensed_features(epics: true)
stub_feature_flags(epic_boards: true)
end
let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
......@@ -108,16 +107,6 @@ RSpec.describe Mutations::Boards::Epics::Create do
end
end
context 'with epic boards disabled' do
before do
stub_feature_flags(epic_boards: false)
end
it 'returns an error' do
expect(subject[:errors]).to include 'This feature is not available'
end
end
context 'with epics not available' do
before do
stub_licensed_features(epics: false)
......
......@@ -46,16 +46,6 @@ RSpec.describe Resolvers::Boards::EpicBoardsResolver do
.to contain_exactly(epic_board2, epic_board1)
.and be_sorted.asc.by(&:name)
end
context 'when epic_boards flag is disabled' do
before do
stub_feature_flags(epic_boards: false)
end
it 'returns nil' do
expect(result).to be_nil
end
end
end
end
end
......
......@@ -69,18 +69,5 @@ RSpec.describe 'get list of epic boards' do
expect(graphql_data.dig('group', 'epicBoard', 'hideClosedList')).to eq board1.hide_closed_list
end
end
context 'when epic_boards flag is disabled' do
before do
stub_feature_flags(epic_boards: false)
end
it 'returns nil epic_boards' do
post_graphql(pagination_query, current_user: current_user)
boards = graphql_data.dig('group', 'epicBoards')
expect(boards).to be_nil
end
end
end
end
......@@ -34,24 +34,9 @@ RSpec.describe 'Reposition and move epic between board lists' do
subject { post_graphql_mutation(mutation(params), current_user: current_user) }
context 'when epic_boards are disabled' do
context 'when epics are available' do
before do
stub_licensed_features(epics: true)
stub_feature_flags(epic_boards: false)
group.add_developer(current_user)
end
it 'raises feature not available error' do
subject
expect(graphql_errors).to include(a_hash_including('message' => 'epic_boards feature is disabled'))
end
end
context 'when epic_boards are enabled' do
before do
stub_licensed_features(epics: true)
stub_feature_flags(epic_boards: true)
end
context 'when user does not have permissions to admin the board' do
......
......@@ -44,7 +44,6 @@ RSpec.describe Mutations::Boards::Epics::Create do
before do
group.add_reporter(current_user)
stub_licensed_features(epics: true)
stub_feature_flags(epic_boards: true)
end
context 'when all arguments are given' do
......
......@@ -10,24 +10,8 @@ RSpec.describe Boards::EpicBoards::CreateService, services: true do
let_it_be(:user) { create(:user) }
let(:parent) { create(:group) }
let(:epic_boards_enabled) { false }
before do
stub_feature_flags(epic_boards: epic_boards_enabled)
end
context 'with epic boards feature not available' do
it 'does not create a board' do
service = described_class.new(parent, user)
expect(service.execute.payload).not_to be_nil
expect { service.execute }.not_to change(parent.epic_boards, :count)
end
end
context 'with epic boards feature available' do
let(:epic_boards_enabled) { true }
context 'create epic board' do
it_behaves_like 'create a board', :epic_boards
it 'tracks epic board creation' do
......
......@@ -12,17 +12,4 @@ RSpec.describe Boards::EpicLists::CreateService do
create(:epic_list, params.merge(epic_board: board))
end
end
context 'when epic_boards feature flag is disabled' do
before do
stub_feature_flags(epic_boards: false)
end
it 'returns an error' do
response = described_class.new(parent, nil).execute(board)
expect(response.success?).to eq(false)
expect(response.errors).to include("Epic boards feature is not enabled.")
end
end
end
......@@ -18,7 +18,6 @@ RSpec.describe Boards::EpicLists::DestroyService do
before do
stub_licensed_features(epics: true)
stub_feature_flags(epic_boards: true)
end
context 'when user does not have permission' do
......@@ -50,18 +49,5 @@ RSpec.describe Boards::EpicLists::DestroyService do
expect(response.errors).to include("Epics feature is not available.")
end
end
context 'when epic_boards feature flag is disabled' do
before do
stub_feature_flags(epic_boards: false)
end
it 'returns an error' do
response = described_class.new(parent, nil).execute(list)
expect(response).to be_error
expect(response.errors).to include("Epic boards feature is not enabled.")
end
end
end
end
......@@ -76,13 +76,5 @@ RSpec.describe Boards::Epics::CreateService do
context 'when epics feature is not available' do
it_behaves_like 'epic creation error', /does not exist or you don't have permission/
end
context 'when epic boards feature flag is not enabled' do
before do
stub_feature_flags(epic_boards: false)
end
it_behaves_like 'epic creation error', /not available/
end
end
end
......@@ -6,7 +6,7 @@ module API
include ::API::ProjectsRelationBuilder
include Gitlab::Utils::StrongMemoize
expose :default_branch, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) }
expose :default_branch_or_main, as: :default_branch, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) }
# Avoids an N+1 query: https://github.com/mbleigh/acts-as-taggable-on/issues/91#issuecomment-168273770
expose :topic_names, as: :tag_list
......
......@@ -22,6 +22,7 @@ module BulkImports
integer_value: integerValue
}
user {
user_gid: id
public_email: publicEmail
}
}
......
......@@ -15,6 +15,9 @@ module BulkImports
def load(context, data)
return unless data
# Current user is already a member
return if data['user_id'].to_i == context.current_user.id
context.group.members.create!(data)
end
end
......
......@@ -6,18 +6,20 @@ module BulkImports
class MemberAttributesTransformer
def transform(context, data)
data
.then { |data| add_user(data) }
.then { |data| add_user(data, context) }
.then { |data| add_access_level(data) }
.then { |data| add_author(data, context) }
end
private
def add_user(data)
def add_user(data, context)
user = find_user(data&.dig('user', 'public_email'))
return unless user
cache_source_user_id(data, user, context)
data
.except('user')
.merge('user_id' => user.id)
......@@ -48,6 +50,16 @@ module BulkImports
data.merge('created_by_id' => context.current_user.id)
end
def cache_source_user_id(data, user, context)
gid = data&.dig('user', 'user_gid')
return unless gid
source_user_id = GlobalID.parse(gid).model_id
::BulkImports::UsersMapper.new(context: context).cache_source_user_id(source_user_id, user.id)
end
end
end
end
......
......@@ -88,11 +88,7 @@ module BulkImports
end
def members_mapper
@members_mapper ||= Gitlab::ImportExport::MembersMapper.new(
exported_members: [],
user: current_user,
importable: portable
)
@members_mapper ||= BulkImports::UsersMapper.new(context: context)
end
end
end
......
# frozen_string_literal: true
module BulkImports
class UsersMapper
include Gitlab::Utils::StrongMemoize
SOURCE_USER_IDS_CACHE_KEY = 'bulk_imports/%{bulk_import}/%{entity}/source_user_ids'
def initialize(context:)
@context = context
@cache_key = SOURCE_USER_IDS_CACHE_KEY % {
bulk_import: @context.bulk_import.id,
entity: @context.entity.id
}
end
def map
strong_memoize(:map) do
map = hash_with_default
cached_source_user_ids.each_pair do |source_id, destination_id|
map[source_id.to_i] = destination_id.to_i
end
map
end
end
def include?(source_user_id)
map.has_key?(source_user_id)
end
def default_user_id
@context.current_user.id
end
def cache_source_user_id(source_id, destination_id)
::Gitlab::Cache::Import::Caching.hash_add(@cache_key, source_id, destination_id)
end
private
def hash_with_default
Hash.new { default_user_id }
end
def cached_source_user_ids
::Gitlab::Cache::Import::Caching.values_from_hash(@cache_key)
end
end
end
......@@ -173,6 +173,34 @@ module Gitlab
val ? true : false
end
# Adds a value to a hash.
#
# raw_key - The key of the hash to add to.
# field - The field to add to the hash.
# value - The field value to add to the hash.
# timeout - The new timeout of the key.
def self.hash_add(raw_key, field, value, timeout: TIMEOUT)
key = cache_key_for(raw_key)
Redis::Cache.with do |redis|
redis.multi do |m|
m.hset(key, field, value)
m.expire(key, timeout)
end
end
end
# Returns the values of the given hash.
#
# raw_key - The key of the set to check.
def self.values_from_hash(raw_key)
key = cache_key_for(raw_key)
Redis::Cache.with do |redis|
redis.hgetall(key)
end
end
def self.cache_key_for(raw_key)
"#{Redis::Cache::CACHE_NAMESPACE}:#{raw_key}"
end
......
......@@ -21611,9 +21611,6 @@ msgstr ""
msgid "NetworkPolicies|Something went wrong, unable to fetch policies"
msgstr ""
msgid "NetworkPolicies|Status"
msgstr ""
msgid "NetworkPolicies|Traffic that does not match any rule will be blocked."
msgstr ""
......
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import { GlSingleStat } from '@gitlab/ui/dist/charts';
import { shallowMount } from '@vue/test-utils';
import MetricCard from '~/analytics/shared/components/metric_card.vue';
import UsageCounts from '~/analytics/usage_trends/components/usage_counts.vue';
import { mockUsageCounts } from '../mock_data';
......@@ -27,18 +28,18 @@ describe('UsageCounts', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
const findMetricCard = () => wrapper.find(MetricCard);
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoading);
const findAllSingleStats = () => wrapper.findAllComponents(GlSingleStat);
describe('while loading', () => {
beforeEach(() => {
createComponent({ loading: true });
});
it('displays the metric card with isLoading=true', () => {
expect(findMetricCard().props('isLoading')).toBe(true);
it('displays a loading indicator', () => {
expect(findSkeletonLoader().exists()).toBe(true);
});
});
......@@ -47,8 +48,15 @@ describe('UsageCounts', () => {
createComponent({ data: { counts: mockUsageCounts } });
});
it('passes the counts data to the metric card', () => {
expect(findMetricCard().props('metrics')).toEqual(mockUsageCounts);
it.each`
index | value | title
${0} | ${mockUsageCounts[0].value} | ${mockUsageCounts[0].label}
${1} | ${mockUsageCounts[1].value} | ${mockUsageCounts[1].label}
`('renders a GlSingleStat for "$title"', ({ index, value, title }) => {
const singleStat = findAllSingleStats().at(index);
expect(singleStat.props('value')).toBe(`${value}`);
expect(singleStat.props('title')).toBe(title);
});
});
});
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::Entities::BasicProjectDetails do
let_it_be(:project) { create(:project) }
let(:current_user) { project.owner }
subject(:output) { described_class.new(project, current_user: current_user).as_json }
describe '#default_branch' do
it 'delegates to Project#default_branch_or_main' do
expect(project).to receive(:default_branch_or_main).twice.and_call_original
expect(output).to include(default_branch: project.default_branch_or_main)
end
context 'anonymous user' do
let(:current_user) { nil }
it 'is not included' do
expect(output.keys).not_to include(:default_branch)
end
end
end
end
......@@ -63,6 +63,14 @@ RSpec.describe BulkImports::Groups::Pipelines::MembersPipeline do
expect(member.updated_at).to eq('2020-01-01T00:00:00Z')
expect(member.expires_at).to eq(nil)
end
context 'when user_id is current user id' do
it 'does not create new member' do
data = { 'user_id' => user.id }
expect { subject.load(context, data) }.not_to change(GroupMember, :count)
end
end
end
describe 'pipeline parts' do
......
......@@ -84,9 +84,34 @@ RSpec.describe BulkImports::Groups::Transformers::MemberAttributesTransformer do
expect(subject.transform(context, data)).to be_nil
end
end
context 'source user id caching' do
context 'when user gid is present' do
it 'caches source user id' do
gid = 'gid://gitlab/User/7'
data = member_data(email: user.email, gid: gid)
expect_next_instance_of(BulkImports::UsersMapper) do |mapper|
expect(mapper).to receive(:cache_source_user_id).with('7', user.id)
end
subject.transform(context, data)
end
end
context 'when user gid is missing' do
it 'does not use caching' do
data = member_data(email: user.email)
expect(BulkImports::UsersMapper).not_to receive(:new)
subject.transform(context, data)
end
end
end
end
def member_data(email: '', access_level: 30)
def member_data(email: '', gid: nil, access_level: 30)
{
'created_at' => '2020-01-01T00:00:00Z',
'updated_at' => '2020-01-01T00:00:00Z',
......@@ -95,6 +120,7 @@ RSpec.describe BulkImports::Groups::Transformers::MemberAttributesTransformer do
'integer_value' => access_level
},
'user' => {
'user_gid' => gid,
'public_email' => email
}
}
......
......@@ -106,8 +106,11 @@ RSpec.describe BulkImports::NdjsonPipeline do
data = [hash, 1]
user = double
config = double(relation_excluded_keys: nil, top_relation_tree: [])
context = double(portable: group, current_user: user, import_export_config: config)
import_double = instance_double(BulkImport, id: 1)
entity_double = instance_double(BulkImports::Entity, id: 2)
context = double(portable: group, current_user: user, import_export_config: config, bulk_import: import_double, entity: entity_double)
allow(subject).to receive(:import_export_config).and_return(config)
allow(subject).to receive(:context).and_return(context)
expect(Gitlab::ImportExport::Group::RelationFactory)
.to receive(:create)
......@@ -116,7 +119,7 @@ RSpec.describe BulkImports::NdjsonPipeline do
relation_sym: :test,
relation_hash: hash,
importable: group,
members_mapper: instance_of(Gitlab::ImportExport::MembersMapper),
members_mapper: instance_of(BulkImports::UsersMapper),
object_builder: Gitlab::ImportExport::Group::ObjectBuilder,
user: user,
excluded_keys: nil
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe BulkImports::UsersMapper do
let_it_be(:user) { create(:user) }
let_it_be(:import) { create(:bulk_import, user: user) }
let_it_be(:entity) { create(:bulk_import_entity, bulk_import: import) }
let(:context) do
instance_double(
BulkImports::Pipeline::Context,
bulk_import: import,
entity: entity,
current_user: user
)
end
subject { described_class.new(context: context) }
describe '#map' do
context 'when value for specified key exists' do
it 'returns a map of source & destination user ids from redis' do
allow(Gitlab::Cache::Import::Caching).to receive(:values_from_hash).and_return({ "1" => "2" })
expect(subject.map).to eq({ 1 => 2 })
end
end
context 'when value for specified key does not exist' do
it 'returns default value' do
expect(subject.map[:non_existent_key]).to eq(user.id)
end
end
end
describe '#default_user_id' do
it 'returns current user id' do
expect(subject.default_user_id).to eq(user.id)
end
end
describe '#include?' do
context 'when source user id is present in the map' do
it 'returns true' do
allow(subject).to receive(:map).and_return({ 1 => 2 })
expect(subject.include?(1)).to eq(true)
end
end
context 'when source user id is missing in the map' do
it 'returns false' do
allow(subject).to receive(:map).and_return({})
expect(subject.include?(1)).to eq(false)
end
end
end
describe '#cache_source_user_id' do
it 'caches provided source & destination user ids in redis' do
expect(Gitlab::Cache::Import::Caching).to receive(:hash_add).with("bulk_imports/#{import.id}/#{entity.id}/source_user_ids", 1, 2)
subject.cache_source_user_id(1, 2)
end
end
end
......@@ -100,6 +100,30 @@ RSpec.describe Gitlab::Cache::Import::Caching, :clean_gitlab_redis_cache do
end
end
describe '.hash_add' do
it 'adds a value to a hash' do
described_class.hash_add('foo', 1, 1)
described_class.hash_add('foo', 2, 2)
key = described_class.cache_key_for('foo')
values = Gitlab::Redis::Cache.with { |r| r.hgetall(key) }
expect(values).to eq({ '1' => '1', '2' => '2' })
end
end
describe '.values_from_hash' do
it 'returns empty hash when the hash is empty' do
expect(described_class.values_from_hash('foo')).to eq({})
end
it 'returns the set list of values' do
described_class.hash_add('foo', 1, 1)
expect(described_class.values_from_hash('foo')).to eq({ '1' => '1' })
end
end
describe '.write_multiple' do
it 'sets multiple keys when key_prefix not set' do
mapping = { 'foo' => 10, 'bar' => 20 }
......
......@@ -1083,6 +1083,14 @@ RSpec.describe Namespace do
end
describe '#all_projects' do
context 'when recursive approach is disabled' do
before do
stub_feature_flags(recursive_approach_for_all_projects: false)
end
include_examples '#all_projects'
end
context 'with use_traversal_ids feature flag enabled' do
before do
stub_feature_flags(use_traversal_ids: true)
......
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