Commit cff478eb authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 008921cd 18d2c779
d5777441938369d512a7825c57ccf6115d7afdfa 21e7c85471a8d7401fad69be300eaff1c0384577
<script> <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 createFlash from '~/flash';
import { number } from '~/lib/utils/unit_format'; import { number } from '~/lib/utils/unit_format';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
...@@ -10,7 +11,8 @@ const defaultPrecision = 0; ...@@ -10,7 +11,8 @@ const defaultPrecision = 0;
export default { export default {
name: 'UsageCounts', name: 'UsageCounts',
components: { components: {
MetricCard, GlSkeletonLoading,
GlSingleStat,
}, },
data() { data() {
return { return {
...@@ -56,10 +58,24 @@ export default { ...@@ -56,10 +58,24 @@ export default {
</script> </script>
<template> <template>
<metric-card <div>
:title="__('Usage Trends')" <h2>
:metrics="counts" {{ __('Usage Trends') }}
:is-loading="$apollo.queries.counts.loading" </h2>
class="gl-mt-4" <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> </template>
...@@ -12,10 +12,8 @@ import BoardColumnDeprecated from './board_column_deprecated.vue'; ...@@ -12,10 +12,8 @@ import BoardColumnDeprecated from './board_column_deprecated.vue';
export default { export default {
components: { components: {
BoardAddNewColumn, BoardAddNewColumn,
BoardColumn: BoardColumn,
gon.features?.graphqlBoardLists || gon.features?.epicBoards BoardColumnDeprecated,
? BoardColumn
: BoardColumnDeprecated,
BoardContentSidebar: () => import('~/boards/components/board_content_sidebar.vue'), BoardContentSidebar: () => import('~/boards/components/board_content_sidebar.vue'),
EpicBoardContentSidebar: () => EpicBoardContentSidebar: () =>
import('ee_component/boards/components/epic_board_content_sidebar.vue'), import('ee_component/boards/components/epic_board_content_sidebar.vue'),
...@@ -38,11 +36,14 @@ export default { ...@@ -38,11 +36,14 @@ export default {
computed: { computed: {
...mapState(['boardLists', 'error', 'addColumnForm']), ...mapState(['boardLists', 'error', 'addColumnForm']),
...mapGetters(['isSwimlanesOn', 'isEpicBoard']), ...mapGetters(['isSwimlanesOn', 'isEpicBoard']),
useNewBoardColumnComponent() {
return this.glFeatures.graphqlBoardLists || this.isSwimlanesOn || this.isEpicBoard;
},
addColumnFormVisible() { addColumnFormVisible() {
return this.addColumnForm?.visible; return this.addColumnForm?.visible;
}, },
boardListsToUse() { boardListsToUse() {
return this.glFeatures.graphqlBoardLists || this.isSwimlanesOn || this.isEpicBoard return this.useNewBoardColumnComponent
? sortBy([...Object.values(this.boardLists)], 'position') ? sortBy([...Object.values(this.boardLists)], 'position')
: this.lists; : this.lists;
}, },
...@@ -65,6 +66,9 @@ export default { ...@@ -65,6 +66,9 @@ export default {
return this.canDragColumns ? options : {}; return this.canDragColumns ? options : {};
}, },
boardColumnComponent() {
return this.useNewBoardColumnComponent ? BoardColumn : BoardColumnDeprecated;
},
}, },
methods: { methods: {
...mapActions(['moveList', 'unsetError']), ...mapActions(['moveList', 'unsetError']),
...@@ -102,7 +106,8 @@ export default { ...@@ -102,7 +106,8 @@ export default {
class="boards-list gl-w-full gl-py-5 gl-px-3 gl-white-space-nowrap" class="boards-list gl-w-full gl-py-5 gl-px-3 gl-white-space-nowrap"
@end="handleDragOnEnd" @end="handleDragOnEnd"
> >
<board-column <component
:is="boardColumnComponent"
v-for="(list, index) in boardListsToUse" v-for="(list, index) in boardListsToUse"
:key="index" :key="index"
ref="board" ref="board"
......
...@@ -279,9 +279,12 @@ class Namespace < ApplicationRecord ...@@ -279,9 +279,12 @@ class Namespace < ApplicationRecord
# Includes projects from this namespace and projects from all subgroups # Includes projects from this namespace and projects from all subgroups
# that belongs to this namespace # that belongs to this namespace
def all_projects def all_projects
namespace = user? ? self : self_and_descendant_ids if Feature.enabled?(:recursive_approach_for_all_projects, default_enabled: :yaml)
namespace = user? ? self : self_and_descendants
Project.where(namespace: namespace) Project.where(namespace: namespace)
else
Project.inside_path(full_path)
end
end end
def has_parent? def has_parent?
......
--- ---
name: epic_boards name: recursive_approach_for_all_projects
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48893 introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64632
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/290039 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/334817
milestone: '13.7' milestone: '14.1'
type: development type: development
group: group::product planning group: group::fulfillment
default_enabled: true default_enabled: true
...@@ -20,7 +20,6 @@ On GitLab.com, we use [separate Redis ...@@ -20,7 +20,6 @@ On GitLab.com, we use [separate Redis
instances](../administration/redis/replication_and_failover.md#running-multiple-redis-clusters). 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) 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. 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 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) 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. ...@@ -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. > - [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. > - [Deployed behind a feature flag](../../user/feature_flags.md), disabled by default.
> - Disabled on GitLab.com. > - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61722) in GitLab 14.1.
> - Not recommended for production use. > - Enabled on GitLab.com.
> - 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)** > - 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 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). [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: ...@@ -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 ## 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 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 but ready for production use. It is deployed behind a feature flag that is
**disabled by default**. **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md) can enable it. [GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md) can opt to disable it.
To enable it: To enable it:
......
...@@ -21,10 +21,11 @@ consumption of your instance. Keep this in mind when adjusting the maximum. ...@@ -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 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: 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. | Value | Definition | Default value | Maximum value |
- **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 patch size** | The total size, in bytes, of the entire diff. | 200 KB | 500 KB |
- **Maximum diff lines**: The total number of lines changed in a diff. The default value is 50,000. | **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 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 collapsed view, with a link to expand the diff. Diffs that exceed any of the
...@@ -35,7 +36,7 @@ To configure these values: ...@@ -35,7 +36,7 @@ To configure these values:
1. On the top bar, select **Menu >** **{admin}** **Admin**. 1. On the top bar, select **Menu >** **{admin}** **Admin**.
1. In the left sidebar, select **Settings > General**. 1. In the left sidebar, select **Settings > General**.
1. Expand **Diff limits**. 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**. 1. Select **Save changes**.
<!-- ## Troubleshooting <!-- ## Troubleshooting
......
...@@ -7,17 +7,16 @@ info: To determine the technical writer assigned to the Stage/Group associated w ...@@ -7,17 +7,16 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Epic Boards **(PREMIUM)** # Epic Boards **(PREMIUM)**
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5067) in GitLab 13.10. > - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5067) in GitLab 13.10.
> - [Deployed behind a feature flag](../../feature_flags.md), disabled by default. > - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/290039) in GitLab 14.1.
> - [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).
Epic boards build on the existing [epic tracking functionality](index.md) and 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](../../project/labels.md). Your epics appear as cards in vertical lists, organized by their assigned
labels. 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) ![GitLab epic board - Premium](img/epic_board_v14_0.png)
...@@ -29,7 +28,8 @@ Prerequisites: ...@@ -29,7 +28,8 @@ Prerequisites:
To create a new epic board: 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. In the upper left corner, select the dropdown with the current board name.
1. Select **Create new board**. 1. Select **Create new board**.
1. Enter the new board's title. 1. Enter the new board's title.
...@@ -77,7 +77,8 @@ Prerequisites: ...@@ -77,7 +77,8 @@ Prerequisites:
To create a new list: 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 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 1. In the **New list** column expand the **Select a label** dropdown and select the label to use as
list scope. list scope.
...@@ -161,22 +162,3 @@ To edit the scope of an epic board: ...@@ -161,22 +162,3 @@ To edit the scope of an epic board:
- Show or hide the Open and Closed columns. - Show or hide the Open and Closed columns.
- Select other labels as the board's scope. - Select other labels as the board's scope.
1. Select **Save changes**. 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> <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 { mapState, mapGetters } from 'vuex';
import { PREDEFINED_NETWORK_POLICIES } from 'ee/threat_monitoring/constants'; import { PREDEFINED_NETWORK_POLICIES } from 'ee/threat_monitoring/constants';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { getTimeago } from '~/lib/utils/datetime_utility'; import { getTimeago } from '~/lib/utils/datetime_utility';
import { setUrlFragment, mergeUrlParams } from '~/lib/utils/url_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 networkPoliciesQuery from '../graphql/queries/network_policies.query.graphql';
import scanExecutionPoliciesQuery from '../graphql/queries/scan_execution_policies.query.graphql'; import scanExecutionPoliciesQuery from '../graphql/queries/scan_execution_policies.query.graphql';
import EnvironmentPicker from './environment_picker.vue'; import EnvironmentPicker from './environment_picker.vue';
...@@ -29,9 +38,13 @@ export default { ...@@ -29,9 +38,13 @@ export default {
GlAlert, GlAlert,
GlSprintf, GlSprintf,
GlLink, GlLink,
GlIcon,
EnvironmentPicker, EnvironmentPicker,
PolicyDrawer, PolicyDrawer,
}, },
directives: {
GlTooltip: GlTooltipDirective,
},
inject: ['projectPath'], inject: ['projectPath'],
props: { props: {
documentationPath: { documentationPath: {
...@@ -130,22 +143,26 @@ export default { ...@@ -130,22 +143,26 @@ export default {
label: s__('NetworkPolicies|Namespace'), label: s__('NetworkPolicies|Namespace'),
}; };
const fields = [ const fields = [
{
key: 'status',
label: '',
thClass: 'gl-w-3',
tdAttr: {
'data-testid': 'policy-status-cell',
},
},
{ {
key: 'name', key: 'name',
label: s__('NetworkPolicies|Name'), label: s__('NetworkPolicies|Name'),
thClass: 'gl-w-half', thClass: 'gl-w-half',
}, },
{
key: 'status',
label: s__('NetworkPolicies|Status'),
},
{ {
key: 'updatedAt', key: 'updatedAt',
label: s__('NetworkPolicies|Last modified'), label: s__('NetworkPolicies|Last modified'),
}, },
]; ];
// Adds column 'namespace' only while 'all environments' option is selected // 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; return fields;
}, },
...@@ -170,12 +187,16 @@ export default { ...@@ -170,12 +187,16 @@ export default {
bTable.clearSelected(); bTable.clearSelected();
}, },
}, },
emptyStateDescription: s__( i18n: {
`NetworkPolicies|Policies are a specification of how groups of pods are allowed to communicate with each other's network endpoints.`, 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}.`, 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> </script>
...@@ -188,7 +209,7 @@ export default { ...@@ -188,7 +209,7 @@ export default {
:dismissible="false" :dismissible="false"
class="gl-mb-3" class="gl-mb-3"
> >
<gl-sprintf :message="$options.autodevopsNoticeDescription"> <gl-sprintf :message="$options.i18n.autodevopsNoticeDescription">
<template #monospaced="{ content }"> <template #monospaced="{ content }">
<span class="gl-font-monospace">{{ content }}</span> <span class="gl-font-monospace">{{ content }}</span>
</template> </template>
...@@ -230,7 +251,14 @@ export default { ...@@ -230,7 +251,14 @@ export default {
@row-selected="presentPolicyDrawer" @row-selected="presentPolicyDrawer"
> >
<template #cell(status)="value"> <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>
<template #cell(updatedAt)="value"> <template #cell(updatedAt)="value">
...@@ -242,7 +270,7 @@ export default { ...@@ -242,7 +270,7 @@ export default {
<gl-empty-state <gl-empty-state
ref="tableEmptyState" ref="tableEmptyState"
:title="s__('NetworkPolicies|No policies detected')" :title="s__('NetworkPolicies|No policies detected')"
:description="$options.emptyStateDescription" :description="$options.i18n.emptyStateDescription"
:primary-button-link="documentationFullPath" :primary-button-link="documentationFullPath"
:primary-button-text="__('Learn more')" :primary-button-text="__('Learn more')"
/> />
......
...@@ -8,9 +8,6 @@ class Groups::EpicBoardsController < Groups::ApplicationController ...@@ -8,9 +8,6 @@ class Groups::EpicBoardsController < Groups::ApplicationController
before_action :redirect_to_recent_board, only: [:index] before_action :redirect_to_recent_board, only: [:index]
before_action :assign_endpoint_vars 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' track_redis_hll_event :index, :show, name: 'g_project_management_users_viewing_epic_boards'
...@@ -70,6 +67,6 @@ class Groups::EpicBoardsController < Groups::ApplicationController ...@@ -70,6 +67,6 @@ class Groups::EpicBoardsController < Groups::ApplicationController
end end
def authorize_read_board! 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
end end
...@@ -50,10 +50,6 @@ module Mutations ...@@ -50,10 +50,6 @@ module Mutations
board = authorized_find!(id: args[:board_id]) board = authorized_find!(id: args[:board_id])
epic = authorized_find!(id: args[:epic_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)) move_epic(board, epic, move_list_arguments(args).merge(board_id: board.id))
{ {
......
...@@ -24,10 +24,6 @@ module Mutations ...@@ -24,10 +24,6 @@ module Mutations
def resolve(**args) def resolve(**args)
board = authorized_find!(id: args[:id]) 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) ::Boards::EpicBoards::UpdateService.new(board.resource_parent, current_user, args).execute(board)
{ {
......
...@@ -16,7 +16,6 @@ module Resolvers ...@@ -16,7 +16,6 @@ module Resolvers
alias_method :group, :object alias_method :group, :object
def resolve(id: nil) def resolve(id: nil)
return unless Feature.enabled?(:epic_boards, group, default_enabled: :yaml)
return unless group.licensed_feature_available?(:epics) return unless group.licensed_feature_available?(:epics)
authorize! authorize!
......
...@@ -8,7 +8,7 @@ module Boards ...@@ -8,7 +8,7 @@ module Boards
override :can_create_board? override :can_create_board?
def can_create_board? def can_create_board?
Feature.enabled?(:epic_boards, parent, default_enabled: :yaml) true
end end
override :parent_board_collection override :parent_board_collection
......
...@@ -7,10 +7,6 @@ module Boards ...@@ -7,10 +7,6 @@ module Boards
override :execute override :execute
def execute(board) 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 super
end end
end end
......
...@@ -11,10 +11,6 @@ module Boards ...@@ -11,10 +11,6 @@ module Boards
return ServiceResponse.error(message: 'Epics feature is not available.') return ServiceResponse.error(message: 'Epics feature is not available.')
end 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) 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 '\ 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') 'exist or you don\'t have permission to perform this action')
......
...@@ -42,7 +42,7 @@ module Boards ...@@ -42,7 +42,7 @@ module Boards
end end
def available? def available?
group.licensed_feature_available?(:epics) && Feature.enabled?(:epic_boards, parent, default_enabled: :yaml) group.licensed_feature_available?(:epics)
end end
def allowed? def allowed?
......
...@@ -20,10 +20,9 @@ ...@@ -20,10 +20,9 @@
= link_to group_epics_path(group), title: 'List' do = link_to group_epics_path(group), title: 'List' do
%span= _('List') %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
= nav_link(path: ['epic_boards#index', 'epic_boards#show'], html_options: { class: "home" }) do = link_to group_epic_boards_path(group), title: 'Boards' do
= link_to group_epic_boards_path(group), title: 'Boards' do %span= _('Boards')
%span= _('Boards')
= nav_link(path: 'roadmap#show', html_options: { class: 'home' }) do = nav_link(path: 'roadmap#show', html_options: { class: 'home' }) do
= link_to group_roadmap_path(group), title: 'Roadmap' 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 ...@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/300271
milestone: '13.9' milestone: '13.9'
type: development type: development
group: group::compliance group: group::compliance
default_enabled: false default_enabled: true
...@@ -80,28 +80,6 @@ RSpec.describe 'Group navbar' do ...@@ -80,28 +80,6 @@ RSpec.describe 'Group navbar' do
end end
context 'when epics are available' do 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 before do
stub_licensed_features(epics: true) stub_licensed_features(epics: true)
......
import { GlSkeletonLoader } from '@gitlab/ui'; import { GlSkeletonLoader } from '@gitlab/ui';
import { GlSingleStat } from '@gitlab/ui/dist/charts'; import { GlSingleStat } from '@gitlab/ui/dist/charts';
import { shallowMount } from '@vue/test-utils'; 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'; import { stats } from '../mock_data';
describe('ThroughputStats', () => { describe('ThroughputStats', () => {
......
import { GlToggle } from '@gitlab/ui'; import { GlToggle } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex'; 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'; import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
const localVue = createLocalVue(); const localVue = createLocalVue();
......
import { shallowMount } from '@vue/test-utils'; 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'; import { scannerProfiles } from 'ee_jest/on_demand_scans/mocks/mock_data';
const [profile] = scannerProfiles; const [profile] = scannerProfiles;
......
import { shallowMount } from '@vue/test-utils'; 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'; import { siteProfiles } from 'ee_jest/on_demand_scans/mocks/mock_data';
describe('DastSiteProfileSummary', () => { describe('DastSiteProfileSummary', () => {
......
...@@ -5,8 +5,8 @@ import AddEditScheduleForm from 'ee/oncall_schedules/components/add_edit_schedul ...@@ -5,8 +5,8 @@ import AddEditScheduleForm from 'ee/oncall_schedules/components/add_edit_schedul
import AddEditScheduleModal, { import AddEditScheduleModal, {
i18n, i18n,
} from 'ee/oncall_schedules/components/add_edit_schedule_modal.vue'; } from 'ee/oncall_schedules/components/add_edit_schedule_modal.vue';
import { editScheduleModalId } from 'ee/oncall_schedules/components/oncall_schedule'; import { editScheduleModalId } from 'ee/oncall_schedules/components/oncall_schedule.vue';
import { addScheduleModalId } from 'ee/oncall_schedules/components/oncall_schedules_wrapper'; 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 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 getOncallSchedulesWithRotationsQuery from 'ee/oncall_schedules/graphql/queries/get_oncall_schedules.query.graphql';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
......
...@@ -4,7 +4,7 @@ import VueApollo from 'vue-apollo'; ...@@ -4,7 +4,7 @@ import VueApollo from 'vue-apollo';
import DeleteScheduleModal, { import DeleteScheduleModal, {
i18n, i18n,
} from 'ee/oncall_schedules/components/delete_schedule_modal.vue'; } 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 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 getOncallSchedulesQuery from 'ee/oncall_schedules/graphql/queries/get_oncall_schedules.query.graphql';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
......
...@@ -77,6 +77,7 @@ describe('PolicyList component', () => { ...@@ -77,6 +77,7 @@ describe('PolicyList component', () => {
const findEnvironmentsPicker = () => wrapper.find({ ref: 'environmentsPicker' }); const findEnvironmentsPicker = () => wrapper.find({ ref: 'environmentsPicker' });
const findPoliciesTable = () => wrapper.findComponent(GlTable); const findPoliciesTable = () => wrapper.findComponent(GlTable);
const findPolicyStatusCells = () => wrapper.findAllByTestId('policy-status-cell');
const findPolicyDrawer = () => wrapper.findByTestId('policyDrawer'); const findPolicyDrawer = () => wrapper.findByTestId('policyDrawer');
const findAutodevopsAlert = () => wrapper.findByTestId('autodevopsAlert'); const findAutodevopsAlert = () => wrapper.findByTestId('autodevopsAlert');
...@@ -159,14 +160,36 @@ describe('PolicyList component', () => { ...@@ -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', () => { describe('with allEnvironments enabled', () => {
beforeEach(() => { beforeEach(() => {
mountWrapper(); mountWrapper();
wrapper.vm.$store.state.threatMonitoring.allEnvironments = true; wrapper.vm.$store.state.threatMonitoring.allEnvironments = true;
}); });
it('renders policies table', () => { it('renders namespace column', () => {
const namespaceHeader = findPoliciesTable().findAll('[role="columnheader"]').at(1); const namespaceHeader = findPoliciesTable().findAll('[role="columnheader"]').at(2);
expect(namespaceHeader.text()).toBe('Namespace'); expect(namespaceHeader.text()).toBe('Namespace');
}); });
}); });
......
...@@ -27,10 +27,9 @@ RSpec.describe ::Mutations::Boards::EpicBoards::Create do ...@@ -27,10 +27,9 @@ RSpec.describe ::Mutations::Boards::EpicBoards::Create do
it { is_expected.to have_graphql_fields(:epic_board).at_least } it { is_expected.to have_graphql_fields(:epic_board).at_least }
end end
context 'with epic feature enabled and epic_boards feature flag enabled' do context 'with epic feature enabled' do
before do before do
stub_licensed_features(epics: true) stub_licensed_features(epics: true)
stub_feature_flags(epic_boards: true)
end end
context 'when user does not have permission to create epic board' do context 'when user does not have permission to create epic board' do
...@@ -52,14 +51,6 @@ RSpec.describe ::Mutations::Boards::EpicBoards::Create do ...@@ -52,14 +51,6 @@ RSpec.describe ::Mutations::Boards::EpicBoards::Create do
end end
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 context 'with epic feature disabled' do
before do before do
stub_licensed_features(epics: false) stub_licensed_features(epics: false)
......
...@@ -42,7 +42,6 @@ RSpec.describe ::Mutations::Boards::EpicBoards::EpicMoveList do ...@@ -42,7 +42,6 @@ RSpec.describe ::Mutations::Boards::EpicBoards::EpicMoveList do
describe '#resolve' do describe '#resolve' do
before do before do
stub_licensed_features(epics: true) stub_licensed_features(epics: true)
stub_feature_flags(epic_boards: true)
end end
context 'when user does not have permissions' do context 'when user does not have permissions' do
......
...@@ -27,10 +27,9 @@ RSpec.describe ::Mutations::Boards::EpicBoards::Update do ...@@ -27,10 +27,9 @@ RSpec.describe ::Mutations::Boards::EpicBoards::Update do
it { is_expected.to have_graphql_fields(:epic_board).at_least } it { is_expected.to have_graphql_fields(:epic_board).at_least }
end end
context 'with epic feature enabled and epic_boards feature flag enabled' do context 'with epic feature enabled' do
before do before do
stub_licensed_features(epics: true) stub_licensed_features(epics: true)
stub_feature_flags(epic_boards: true)
end end
context 'when user does not have permission to update epic board' do context 'when user does not have permission to update epic board' do
...@@ -50,14 +49,6 @@ RSpec.describe ::Mutations::Boards::EpicBoards::Update do ...@@ -50,14 +49,6 @@ RSpec.describe ::Mutations::Boards::EpicBoards::Update do
expect(result[:epic_board].hide_backlog_list).to be_truthy expect(result[:epic_board].hide_backlog_list).to be_truthy
end end
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 end
describe '#ready?' do describe '#ready?' do
......
...@@ -18,7 +18,6 @@ RSpec.describe Mutations::Boards::Epics::Create do ...@@ -18,7 +18,6 @@ RSpec.describe Mutations::Boards::Epics::Create do
before do before do
stub_licensed_features(epics: true) stub_licensed_features(epics: true)
stub_feature_flags(epic_boards: true)
end end
let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
...@@ -108,16 +107,6 @@ RSpec.describe Mutations::Boards::Epics::Create do ...@@ -108,16 +107,6 @@ RSpec.describe Mutations::Boards::Epics::Create do
end end
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 context 'with epics not available' do
before do before do
stub_licensed_features(epics: false) stub_licensed_features(epics: false)
......
...@@ -46,16 +46,6 @@ RSpec.describe Resolvers::Boards::EpicBoardsResolver do ...@@ -46,16 +46,6 @@ RSpec.describe Resolvers::Boards::EpicBoardsResolver do
.to contain_exactly(epic_board2, epic_board1) .to contain_exactly(epic_board2, epic_board1)
.and be_sorted.asc.by(&:name) .and be_sorted.asc.by(&:name)
end 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 end
end end
......
...@@ -69,18 +69,5 @@ RSpec.describe 'get list of epic boards' do ...@@ -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 expect(graphql_data.dig('group', 'epicBoard', 'hideClosedList')).to eq board1.hide_closed_list
end end
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
end end
...@@ -34,24 +34,9 @@ RSpec.describe 'Reposition and move epic between board lists' do ...@@ -34,24 +34,9 @@ RSpec.describe 'Reposition and move epic between board lists' do
subject { post_graphql_mutation(mutation(params), current_user: current_user) } 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 before do
stub_licensed_features(epics: true) 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 end
context 'when user does not have permissions to admin the board' do context 'when user does not have permissions to admin the board' do
......
...@@ -44,7 +44,6 @@ RSpec.describe Mutations::Boards::Epics::Create do ...@@ -44,7 +44,6 @@ RSpec.describe Mutations::Boards::Epics::Create do
before do before do
group.add_reporter(current_user) group.add_reporter(current_user)
stub_licensed_features(epics: true) stub_licensed_features(epics: true)
stub_feature_flags(epic_boards: true)
end end
context 'when all arguments are given' do context 'when all arguments are given' do
......
...@@ -10,24 +10,8 @@ RSpec.describe Boards::EpicBoards::CreateService, services: true do ...@@ -10,24 +10,8 @@ RSpec.describe Boards::EpicBoards::CreateService, services: true do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:parent) { create(:group) } 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_behaves_like 'create a board', :epic_boards
it 'tracks epic board creation' do it 'tracks epic board creation' do
......
...@@ -12,17 +12,4 @@ RSpec.describe Boards::EpicLists::CreateService do ...@@ -12,17 +12,4 @@ RSpec.describe Boards::EpicLists::CreateService do
create(:epic_list, params.merge(epic_board: board)) create(:epic_list, params.merge(epic_board: board))
end end
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 end
...@@ -18,7 +18,6 @@ RSpec.describe Boards::EpicLists::DestroyService do ...@@ -18,7 +18,6 @@ RSpec.describe Boards::EpicLists::DestroyService do
before do before do
stub_licensed_features(epics: true) stub_licensed_features(epics: true)
stub_feature_flags(epic_boards: true)
end end
context 'when user does not have permission' do context 'when user does not have permission' do
...@@ -50,18 +49,5 @@ RSpec.describe Boards::EpicLists::DestroyService do ...@@ -50,18 +49,5 @@ RSpec.describe Boards::EpicLists::DestroyService do
expect(response.errors).to include("Epics feature is not available.") expect(response.errors).to include("Epics feature is not available.")
end end
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
end end
...@@ -76,13 +76,5 @@ RSpec.describe Boards::Epics::CreateService do ...@@ -76,13 +76,5 @@ RSpec.describe Boards::Epics::CreateService do
context 'when epics feature is not available' do context 'when epics feature is not available' do
it_behaves_like 'epic creation error', /does not exist or you don't have permission/ it_behaves_like 'epic creation error', /does not exist or you don't have permission/
end 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
end end
...@@ -6,7 +6,7 @@ module API ...@@ -6,7 +6,7 @@ module API
include ::API::ProjectsRelationBuilder include ::API::ProjectsRelationBuilder
include Gitlab::Utils::StrongMemoize 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 # Avoids an N+1 query: https://github.com/mbleigh/acts-as-taggable-on/issues/91#issuecomment-168273770
expose :topic_names, as: :tag_list expose :topic_names, as: :tag_list
......
...@@ -22,6 +22,7 @@ module BulkImports ...@@ -22,6 +22,7 @@ module BulkImports
integer_value: integerValue integer_value: integerValue
} }
user { user {
user_gid: id
public_email: publicEmail public_email: publicEmail
} }
} }
......
...@@ -15,6 +15,9 @@ module BulkImports ...@@ -15,6 +15,9 @@ module BulkImports
def load(context, data) def load(context, data)
return unless 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) context.group.members.create!(data)
end end
end end
......
...@@ -6,18 +6,20 @@ module BulkImports ...@@ -6,18 +6,20 @@ module BulkImports
class MemberAttributesTransformer class MemberAttributesTransformer
def transform(context, data) def transform(context, data)
data data
.then { |data| add_user(data) } .then { |data| add_user(data, context) }
.then { |data| add_access_level(data) } .then { |data| add_access_level(data) }
.then { |data| add_author(data, context) } .then { |data| add_author(data, context) }
end end
private private
def add_user(data) def add_user(data, context)
user = find_user(data&.dig('user', 'public_email')) user = find_user(data&.dig('user', 'public_email'))
return unless user return unless user
cache_source_user_id(data, user, context)
data data
.except('user') .except('user')
.merge('user_id' => user.id) .merge('user_id' => user.id)
...@@ -48,6 +50,16 @@ module BulkImports ...@@ -48,6 +50,16 @@ module BulkImports
data.merge('created_by_id' => context.current_user.id) data.merge('created_by_id' => context.current_user.id)
end 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 end
end end
......
...@@ -88,11 +88,7 @@ module BulkImports ...@@ -88,11 +88,7 @@ module BulkImports
end end
def members_mapper def members_mapper
@members_mapper ||= Gitlab::ImportExport::MembersMapper.new( @members_mapper ||= BulkImports::UsersMapper.new(context: context)
exported_members: [],
user: current_user,
importable: portable
)
end end
end 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 ...@@ -173,6 +173,34 @@ module Gitlab
val ? true : false val ? true : false
end 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) def self.cache_key_for(raw_key)
"#{Redis::Cache::CACHE_NAMESPACE}:#{raw_key}" "#{Redis::Cache::CACHE_NAMESPACE}:#{raw_key}"
end end
......
...@@ -21611,9 +21611,6 @@ msgstr "" ...@@ -21611,9 +21611,6 @@ msgstr ""
msgid "NetworkPolicies|Something went wrong, unable to fetch policies" msgid "NetworkPolicies|Something went wrong, unable to fetch policies"
msgstr "" msgstr ""
msgid "NetworkPolicies|Status"
msgstr ""
msgid "NetworkPolicies|Traffic that does not match any rule will be blocked." msgid "NetworkPolicies|Traffic that does not match any rule will be blocked."
msgstr "" msgstr ""
......
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import { GlSingleStat } from '@gitlab/ui/dist/charts';
import { shallowMount } from '@vue/test-utils'; 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 UsageCounts from '~/analytics/usage_trends/components/usage_counts.vue';
import { mockUsageCounts } from '../mock_data'; import { mockUsageCounts } from '../mock_data';
...@@ -27,18 +28,18 @@ describe('UsageCounts', () => { ...@@ -27,18 +28,18 @@ describe('UsageCounts', () => {
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
}); });
const findMetricCard = () => wrapper.find(MetricCard); const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoading);
const findAllSingleStats = () => wrapper.findAllComponents(GlSingleStat);
describe('while loading', () => { describe('while loading', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ loading: true }); createComponent({ loading: true });
}); });
it('displays the metric card with isLoading=true', () => { it('displays a loading indicator', () => {
expect(findMetricCard().props('isLoading')).toBe(true); expect(findSkeletonLoader().exists()).toBe(true);
}); });
}); });
...@@ -47,8 +48,15 @@ describe('UsageCounts', () => { ...@@ -47,8 +48,15 @@ describe('UsageCounts', () => {
createComponent({ data: { counts: mockUsageCounts } }); createComponent({ data: { counts: mockUsageCounts } });
}); });
it('passes the counts data to the metric card', () => { it.each`
expect(findMetricCard().props('metrics')).toEqual(mockUsageCounts); 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 ...@@ -63,6 +63,14 @@ RSpec.describe BulkImports::Groups::Pipelines::MembersPipeline do
expect(member.updated_at).to eq('2020-01-01T00:00:00Z') expect(member.updated_at).to eq('2020-01-01T00:00:00Z')
expect(member.expires_at).to eq(nil) expect(member.expires_at).to eq(nil)
end 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 end
describe 'pipeline parts' do describe 'pipeline parts' do
......
...@@ -84,9 +84,34 @@ RSpec.describe BulkImports::Groups::Transformers::MemberAttributesTransformer do ...@@ -84,9 +84,34 @@ RSpec.describe BulkImports::Groups::Transformers::MemberAttributesTransformer do
expect(subject.transform(context, data)).to be_nil expect(subject.transform(context, data)).to be_nil
end end
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 end
def member_data(email: '', access_level: 30) def member_data(email: '', gid: nil, access_level: 30)
{ {
'created_at' => '2020-01-01T00:00:00Z', 'created_at' => '2020-01-01T00:00:00Z',
'updated_at' => '2020-01-01T00:00:00Z', 'updated_at' => '2020-01-01T00:00:00Z',
...@@ -95,6 +120,7 @@ RSpec.describe BulkImports::Groups::Transformers::MemberAttributesTransformer do ...@@ -95,6 +120,7 @@ RSpec.describe BulkImports::Groups::Transformers::MemberAttributesTransformer do
'integer_value' => access_level 'integer_value' => access_level
}, },
'user' => { 'user' => {
'user_gid' => gid,
'public_email' => email 'public_email' => email
} }
} }
......
...@@ -106,8 +106,11 @@ RSpec.describe BulkImports::NdjsonPipeline do ...@@ -106,8 +106,11 @@ RSpec.describe BulkImports::NdjsonPipeline do
data = [hash, 1] data = [hash, 1]
user = double user = double
config = double(relation_excluded_keys: nil, top_relation_tree: []) 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(:import_export_config).and_return(config)
allow(subject).to receive(:context).and_return(context)
expect(Gitlab::ImportExport::Group::RelationFactory) expect(Gitlab::ImportExport::Group::RelationFactory)
.to receive(:create) .to receive(:create)
...@@ -116,7 +119,7 @@ RSpec.describe BulkImports::NdjsonPipeline do ...@@ -116,7 +119,7 @@ RSpec.describe BulkImports::NdjsonPipeline do
relation_sym: :test, relation_sym: :test,
relation_hash: hash, relation_hash: hash,
importable: group, importable: group,
members_mapper: instance_of(Gitlab::ImportExport::MembersMapper), members_mapper: instance_of(BulkImports::UsersMapper),
object_builder: Gitlab::ImportExport::Group::ObjectBuilder, object_builder: Gitlab::ImportExport::Group::ObjectBuilder,
user: user, user: user,
excluded_keys: nil 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 ...@@ -100,6 +100,30 @@ RSpec.describe Gitlab::Cache::Import::Caching, :clean_gitlab_redis_cache do
end end
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 describe '.write_multiple' do
it 'sets multiple keys when key_prefix not set' do it 'sets multiple keys when key_prefix not set' do
mapping = { 'foo' => 10, 'bar' => 20 } mapping = { 'foo' => 10, 'bar' => 20 }
......
...@@ -1083,6 +1083,14 @@ RSpec.describe Namespace do ...@@ -1083,6 +1083,14 @@ RSpec.describe Namespace do
end end
describe '#all_projects' do 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 context 'with use_traversal_ids feature flag enabled' do
before do before do
stub_feature_flags(use_traversal_ids: true) 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