Commit 152741c4 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 8cce1cda 9a2aee44
import { masks } from 'dateformat';
export const DATE_RANGE_LIMIT = 180; export const DATE_RANGE_LIMIT = 180;
export const OFFSET_DATE_BY_ONE = 1; export const OFFSET_DATE_BY_ONE = 1;
export const PROJECTS_PER_PAGE = 50; export const PROJECTS_PER_PAGE = 50;
const { isoDate, mediumDate } = masks;
export const dateFormats = {
isoDate,
defaultDate: mediumDate,
defaultDateTime: 'mmm d, yyyy h:MMtt',
};
...@@ -16,7 +16,7 @@ module Resolvers ...@@ -16,7 +16,7 @@ module Resolvers
filter_params = item_filters(args[:filters]).merge(board_id: list.board.id, id: list.id) filter_params = item_filters(args[:filters]).merge(board_id: list.board.id, id: list.id)
service = ::Boards::Issues::ListService.new(list.board.resource_parent, context[:current_user], filter_params) service = ::Boards::Issues::ListService.new(list.board.resource_parent, context[:current_user], filter_params)
offset_pagination(service.execute) service.execute
end end
# https://gitlab.com/gitlab-org/gitlab/-/issues/235681 # https://gitlab.com/gitlab-org/gitlab/-/issues/235681
......
...@@ -332,12 +332,15 @@ module Issuable ...@@ -332,12 +332,15 @@ module Issuable
# When using CTE make sure to select the same columns that are on the group_by clause. # When using CTE make sure to select the same columns that are on the group_by clause.
# This prevents errors when ignored columns are present in the database. # This prevents errors when ignored columns are present in the database.
issuable_columns = with_cte ? issue_grouping_columns(use_cte: with_cte) : "#{table_name}.*" issuable_columns = with_cte ? issue_grouping_columns(use_cte: with_cte) : "#{table_name}.*"
group_columns = issue_grouping_columns(use_cte: with_cte) + ["highest_priorities.label_priority"]
extra_select_columns.unshift("(#{highest_priority}) AS highest_priority") extra_select_columns.unshift("highest_priorities.label_priority as highest_priority")
select(issuable_columns) select(issuable_columns)
.select(extra_select_columns) .select(extra_select_columns)
.group(issue_grouping_columns(use_cte: with_cte)) .from("#{table_name}")
.joins("JOIN LATERAL(#{highest_priority}) as highest_priorities ON TRUE")
.group(group_columns)
.reorder(Gitlab::Database.nulls_last_order('highest_priority', direction)) .reorder(Gitlab::Database.nulls_last_order('highest_priority', direction))
end end
...@@ -384,7 +387,7 @@ module Issuable ...@@ -384,7 +387,7 @@ module Issuable
if use_cte if use_cte
attribute_names.map { |attr| arel_table[attr.to_sym] } attribute_names.map { |attr| arel_table[attr.to_sym] }
else else
arel_table[:id] [arel_table[:id]]
end end
end end
......
...@@ -46,7 +46,7 @@ module Sortable ...@@ -46,7 +46,7 @@ module Sortable
private private
def highest_label_priority(target_type_column: nil, target_type: nil, target_column:, project_column:, excluded_labels: []) def highest_label_priority(target_type_column: nil, target_type: nil, target_column:, project_column:, excluded_labels: [])
query = Label.select(LabelPriority.arel_table[:priority].minimum) query = Label.select(LabelPriority.arel_table[:priority].minimum.as('label_priority'))
.left_join_priorities .left_join_priorities
.joins(:label_links) .joins(:label_links)
.where("label_priorities.project_id = #{project_column}") .where("label_priorities.project_id = #{project_column}")
......
...@@ -268,10 +268,41 @@ class Issue < ApplicationRecord ...@@ -268,10 +268,41 @@ class Issue < ApplicationRecord
# `with_cte` argument allows sorting when using CTE queries and prevents # `with_cte` argument allows sorting when using CTE queries and prevents
# errors in postgres when using CTE search optimisation # errors in postgres when using CTE search optimisation
def self.order_by_position_and_priority(with_cte: false) def self.order_by_position_and_priority(with_cte: false)
order = Gitlab::Pagination::Keyset::Order.build([column_order_relative_position, column_order_highest_priority, column_order_id_desc])
order_labels_priority(with_cte: with_cte) order_labels_priority(with_cte: with_cte)
.reorder(Gitlab::Database.nulls_last_order('relative_position', 'ASC'), .reorder(order)
Gitlab::Database.nulls_last_order('highest_priority', 'ASC'), end
"id DESC")
def self.column_order_relative_position
Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
attribute_name: 'relative_position',
column_expression: arel_table[:relative_position],
order_expression: Gitlab::Database.nulls_last_order('issues.relative_position', 'ASC'),
reversed_order_expression: Gitlab::Database.nulls_last_order('issues.relative_position', 'DESC'),
order_direction: :asc,
nullable: :nulls_last,
distinct: false
)
end
def self.column_order_highest_priority
Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
attribute_name: 'highest_priority',
column_expression: Arel.sql('highest_priorities.label_priority'),
order_expression: Gitlab::Database.nulls_last_order('highest_priorities.label_priority', 'ASC'),
reversed_order_expression: Gitlab::Database.nulls_last_order('highest_priorities.label_priority', 'DESC'),
order_direction: :asc,
nullable: :nulls_last,
distinct: false
)
end
def self.column_order_id_desc
Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
attribute_name: 'id',
order_expression: arel_table[:id].desc
)
end end
# Temporary disable moving null elements because of performance problems # Temporary disable moving null elements because of performance problems
......
...@@ -7,14 +7,8 @@ type: reference, api ...@@ -7,14 +7,8 @@ type: reference, api
# External Status Checks API **(ULTIMATE)** # External Status Checks API **(ULTIMATE)**
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3869) in GitLab 14.0. > - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3869) in GitLab 14.0, disabled behind the `:ff_external_status_checks` feature flag.
> - It's [deployed behind a feature flag](../user/feature_flags.md), disabled by default. > - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/320783) in GitLab 14.1.
> - It's disabled on GitLab.com.
> - It's not recommended for production use.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-external-status-checks).
WARNING:
This feature might not be available to you. Check the **version history** note above for details.
## List status checks for a merge request ## List status checks for a merge request
...@@ -151,35 +145,6 @@ PUT /projects/:id/external_status_checks/:check_id ...@@ -151,35 +145,6 @@ PUT /projects/:id/external_status_checks/:check_id
| `external_url` | string | no | URL of external status check resource | | `external_url` | string | no | URL of external status check resource |
| `protected_branch_ids` | `array<Integer>` | no | IDs of protected branches to scope the rule by | | `protected_branch_ids` | `array<Integer>` | no | IDs of protected branches to scope the rule by |
## Enable or disable external status checks **(ULTIMATE SELF)**
External status checks are:
- Under development.
- Not ready for production use.
The feature is deployed behind a feature flag that is **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../user/feature_flags.md)
can enable it.
To enable it:
```ruby
# For the instance
Feature.enable(:ff_external_status_checks)
# For a single project
Feature.enable(:ff_external_status_checks, Project.find(<project id>))
```
To disable it:
```ruby
# For the instance
Feature.disable(:ff_external_status_checks)
# For a single project
Feature.disable(:ff_external_status_checks, Project.find(<project id>))
```
## Related links ## Related links
- [External status checks](../user/project/merge_requests/status_checks.md). - [External status checks](../user/project/merge_requests/status_checks.md).
...@@ -8,11 +8,8 @@ disqus_identifier: 'https://docs.gitlab.com/ee/user/project/merge_requests/statu ...@@ -8,11 +8,8 @@ disqus_identifier: 'https://docs.gitlab.com/ee/user/project/merge_requests/statu
# External Status Checks **(ULTIMATE)** # External Status Checks **(ULTIMATE)**
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3869) in GitLab 14.0. > - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3869) in GitLab 14.0, disabled behind the `:ff_external_status_checks` feature flag.
> - It's [deployed behind a feature flag](../../feature_flags.md), disabled by default. > - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/320783) in GitLab 14.1.
> - It's disabled on GitLab.com.
> - It's not recommended for production use.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-status-checks). **(ULTIMATE SELF)**
WARNING: WARNING:
This feature might not be available to you. Check the **version history** note above for details. This feature might not be available to you. Check the **version history** note above for details.
...@@ -116,7 +113,6 @@ the status check and it **will not** be recoverable. ...@@ -116,7 +113,6 @@ the status check and it **will not** be recoverable.
## Status checks widget ## Status checks widget
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/327634) in GitLab 14.1. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/327634) in GitLab 14.1.
> - The [external status checks](#external-status-checks) feature must be [enabled](#enable-or-disable-status-checks) to see the status checks widget.
The status checks widget displays in merge requests and shows the status of external The status checks widget displays in merge requests and shows the status of external
status checks: status checks:
...@@ -188,31 +184,6 @@ You should: ...@@ -188,31 +184,6 @@ You should:
- Check the [GitLab status page](https://status.gitlab.com/) if the problem persists, - Check the [GitLab status page](https://status.gitlab.com/) if the problem persists,
to see if there is a wider outage. to see if there is a wider outage.
## Enable or disable status checks **(ULTIMATE SELF)**
Status checks are 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.
To enable it:
```ruby
# For the instance
Feature.enable(:ff_external_status_checks)
# For a single project
Feature.enable(:ff_external_status_checks, Project.find(<project id>))
```
To disable it:
```ruby
# For the instance
Feature.disable(:ff_external_status_checks)
# For a single project
Feature.disable(:ff_external_status_checks, Project.find(<project id>)
```
## Related links ## Related links
- [External status checks API](../../../api/status_checks.md) - [External status checks API](../../../api/status_checks.md)
<script> <script>
import { GlAlert } from '@gitlab/ui'; import { GlAlert } from '@gitlab/ui';
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import { dateFormats } from '~/analytics/shared/constants';
import { __ } from '~/locale'; import { __ } from '~/locale';
import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue'; import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
import Scatterplot from '../../shared/components/scatterplot.vue'; import Scatterplot from '../../shared/components/scatterplot.vue';
import { dateFormats } from '../../shared/constants';
import StageDropdownFilter from './stage_dropdown_filter.vue'; import StageDropdownFilter from './stage_dropdown_filter.vue';
export default { export default {
......
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
import { isNumber } from 'lodash'; import { isNumber } from 'lodash';
import { dateFormats } from '~/analytics/shared/constants';
import { OVERVIEW_STAGE_ID } from '~/cycle_analytics/constants'; import { OVERVIEW_STAGE_ID } from '~/cycle_analytics/constants';
import { pathNavigationData as basePathNavigationData } from '~/cycle_analytics/store/getters'; import { pathNavigationData as basePathNavigationData } from '~/cycle_analytics/store/getters';
import { filterStagesByHiddenStatus } from '~/cycle_analytics/utils'; import { filterStagesByHiddenStatus } from '~/cycle_analytics/utils';
import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import httpStatus from '~/lib/utils/http_status'; import httpStatus from '~/lib/utils/http_status';
import { filterToQueryObject } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils'; import { filterToQueryObject } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
import { dateFormats } from '../../shared/constants';
import { DEFAULT_VALUE_STREAM_ID, OVERVIEW_STAGE_CONFIG, PAGINATION_TYPE } from '../constants'; import { DEFAULT_VALUE_STREAM_ID, OVERVIEW_STAGE_CONFIG, PAGINATION_TYPE } from '../constants';
export const hasNoAccessError = (state) => state.errorCode === httpStatus.FORBIDDEN; export const hasNoAccessError = (state) => state.errorCode === httpStatus.FORBIDDEN;
......
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
import { isNumber } from 'lodash'; import { isNumber } from 'lodash';
import { dateFormats } from '~/analytics/shared/constants';
import { OVERVIEW_STAGE_ID } from '~/cycle_analytics/constants'; import { OVERVIEW_STAGE_ID } from '~/cycle_analytics/constants';
import { medianTimeToParsedSeconds } from '~/cycle_analytics/utils'; import { medianTimeToParsedSeconds } from '~/cycle_analytics/utils';
import createFlash, { hideFlash } from '~/flash'; import createFlash, { hideFlash } from '~/flash';
...@@ -7,7 +8,6 @@ import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; ...@@ -7,7 +8,6 @@ import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { newDate, dayAfter, secondsToDays, getDatesInRange } from '~/lib/utils/datetime_utility'; import { newDate, dayAfter, secondsToDays, getDatesInRange } from '~/lib/utils/datetime_utility';
import httpStatus from '~/lib/utils/http_status'; import httpStatus from '~/lib/utils/http_status';
import { convertToSnakeCase, slugify } from '~/lib/utils/text_utility'; import { convertToSnakeCase, slugify } from '~/lib/utils/text_utility';
import { dateFormats } from '../shared/constants';
import { toYmd } from '../shared/utils'; import { toYmd } from '../shared/utils';
const EVENT_TYPE_LABEL = 'label'; const EVENT_TYPE_LABEL = 'label';
......
<script> <script>
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
import DateRange from '~/analytics/shared/components/daterange.vue'; import DateRange from '~/analytics/shared/components/daterange.vue';
import { dateFormats } from '~/analytics/shared/constants';
import UrlSync from '~/vue_shared/components/url_sync.vue'; import UrlSync from '~/vue_shared/components/url_sync.vue';
import { dateFormats } from '../../shared/constants';
import { DEFAULT_NUMBER_OF_DAYS } from '../constants'; import { DEFAULT_NUMBER_OF_DAYS } from '../constants';
import FilterBar from './filter_bar.vue'; import FilterBar from './filter_bar.vue';
import ThroughputChart from './throughput_chart.vue'; import ThroughputChart from './throughput_chart.vue';
......
...@@ -13,10 +13,10 @@ import { ...@@ -13,10 +13,10 @@ import {
} from '@gitlab/ui'; } from '@gitlab/ui';
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
import { mapState } from 'vuex'; import { mapState } from 'vuex';
import { dateFormats } from '~/analytics/shared/constants';
import { approximateDuration, differenceInSeconds } from '~/lib/utils/datetime_utility'; import { approximateDuration, differenceInSeconds } from '~/lib/utils/datetime_utility';
import { s__, n__ } from '~/locale'; import { s__, n__ } from '~/locale';
import { filterToQueryObject } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils'; import { filterToQueryObject } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
import { dateFormats } from '../../shared/constants';
import { import {
THROUGHPUT_TABLE_STRINGS, THROUGHPUT_TABLE_STRINGS,
MERGE_REQUEST_ID_PREFIX, MERGE_REQUEST_ID_PREFIX,
......
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
import { dateFormats } from '~/analytics/shared/constants';
import { import {
getMonthNames, getMonthNames,
getDateInPast, getDateInPast,
getDayDifference, getDayDifference,
secondsToDays, secondsToDays,
} from '~/lib/utils/datetime_utility'; } from '~/lib/utils/datetime_utility';
import { dateFormats } from '../shared/constants';
import { THROUGHPUT_CHART_STRINGS, DEFAULT_NUMBER_OF_DAYS, UNITS } from './constants'; import { THROUGHPUT_CHART_STRINGS, DEFAULT_NUMBER_OF_DAYS, UNITS } from './constants';
/** /**
......
...@@ -12,10 +12,10 @@ import { ...@@ -12,10 +12,10 @@ import {
import { GlColumnChart } from '@gitlab/ui/dist/charts'; import { GlColumnChart } from '@gitlab/ui/dist/charts';
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
import { mapState, mapActions, mapGetters } from 'vuex'; import { mapState, mapActions, mapGetters } from 'vuex';
import { dateFormats } from '~/analytics/shared/constants';
import { beginOfDayTime, endOfDayTime } from '~/lib/utils/datetime_utility'; import { beginOfDayTime, endOfDayTime } from '~/lib/utils/datetime_utility';
import featureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import featureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import Scatterplot from '../../shared/components/scatterplot.vue'; import Scatterplot from '../../shared/components/scatterplot.vue';
import { dateFormats } from '../../shared/constants';
import urlSyncMixin from '../../shared/mixins/url_sync_mixin'; import urlSyncMixin from '../../shared/mixins/url_sync_mixin';
import { chartKeys } from '../constants'; import { chartKeys } from '../constants';
import MetricChart from './metric_chart.vue'; import MetricChart from './metric_chart.vue';
......
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
import { dateFormats } from '~/analytics/shared/constants';
import { getDateInPast, beginOfDayTime, endOfDayTime } from '~/lib/utils/datetime_utility'; import { getDateInPast, beginOfDayTime, endOfDayTime } from '~/lib/utils/datetime_utility';
import { dateFormats } from '../../../../shared/constants';
import { chartKeys, scatterPlotAddonQueryDays } from '../../../constants'; import { chartKeys, scatterPlotAddonQueryDays } from '../../../constants';
/** /**
......
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
import { flatten } from 'lodash'; import { flatten } from 'lodash';
import { dateFormats } from '~/analytics/shared/constants';
import { getDayDifference, getDateInPast } from '~/lib/utils/datetime_utility'; import { getDayDifference, getDateInPast } from '~/lib/utils/datetime_utility';
import { median } from '~/lib/utils/number_utils'; import { median } from '~/lib/utils/number_utils';
import { dateFormats } from '../shared/constants';
/** /**
* Gets the labels endpoint for a given group or project * Gets the labels endpoint for a given group or project
......
<script> <script>
import { GlDiscreteScatterChart } from '@gitlab/ui/dist/charts'; import { GlDiscreteScatterChart } from '@gitlab/ui/dist/charts';
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
import { scatterChartLineProps, dateFormats } from '../constants'; import { dateFormats } from '~/analytics/shared/constants';
import { scatterChartLineProps } from '../constants';
export default { export default {
components: { components: {
......
import { masks } from 'dateformat';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
const { isoDate, mediumDate } = masks;
export const dateFormats = {
isoDate,
defaultDate: mediumDate,
defaultDateTime: 'mmm d, yyyy h:MMtt',
};
export const scatterChartLineProps = { export const scatterChartLineProps = {
default: { default: {
type: 'line', type: 'line',
......
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
import { dateFormats } from '~/analytics/shared/constants';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility'; import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
import { dateFormats } from './constants';
export const toYmd = (date) => dateFormat(date, dateFormats.isoDate); export const toYmd = (date) => dateFormat(date, dateFormats.isoDate);
......
...@@ -45,6 +45,14 @@ export default { ...@@ -45,6 +45,14 @@ export default {
hasBranchDetails(mergeRequest) { hasBranchDetails(mergeRequest) {
return mergeRequest.target_branch && mergeRequest.source_branch; return mergeRequest.target_branch && mergeRequest.source_branch;
}, },
onRowClick(e, mergeRequest) {
const link = e.target.closest('a');
// Only toggle the drawer if the element isn't a link
if (!link) {
this.$emit('toggleDrawer', mergeRequest);
}
},
}, },
strings: { strings: {
approvalStatusLabel: __('Approval Status'), approvalStatusLabel: __('Approval Status'),
...@@ -72,12 +80,14 @@ export default { ...@@ -72,12 +80,14 @@ export default {
<!-- TODO: Remove the if/else and duplicate components with https://gitlab.com/gitlab-org/gitlab/-/issues/334682 --> <!-- TODO: Remove the if/else and duplicate components with https://gitlab.com/gitlab-org/gitlab/-/issues/334682 -->
<template v-if="drawerEnabled"> <template v-if="drawerEnabled">
<a <div
v-for="mergeRequest in mergeRequests" v-for="mergeRequest in mergeRequests"
:key="mergeRequest.id" :key="mergeRequest.id"
class="dashboard-merge-request dashboard-grid gl-display-grid gl-grid-tpl-rows-auto gl-hover-bg-blue-50 gl-hover-text-decoration-none gl-hover-cursor-pointer" class="dashboard-merge-request dashboard-grid gl-display-grid gl-grid-tpl-rows-auto gl-hover-bg-blue-50 gl-hover-text-decoration-none gl-hover-cursor-pointer"
data-testid="merge-request-link" data-testid="merge-request-drawer-toggle"
@click="$emit('toggleDrawer', mergeRequest)" tabindex="0"
@click="onRowClick($event, mergeRequest)"
@keypress.enter="onRowClick($event, mergeRequest)"
> >
<merge-request <merge-request
:key="key(mergeRequest.id, $options.keyTypes.mergeRequest)" :key="key(mergeRequest.id, $options.keyTypes.mergeRequest)"
...@@ -123,7 +133,7 @@ export default { ...@@ -123,7 +133,7 @@ export default {
</template> </template>
</time-ago-tooltip> </time-ago-tooltip>
</div> </div>
</a> </div>
</template> </template>
<template v-else> <template v-else>
<template v-for="mergeRequest in mergeRequests"> <template v-for="mergeRequest in mergeRequests">
......
...@@ -19,7 +19,7 @@ export const COLORS = { ...@@ -19,7 +19,7 @@ export const COLORS = {
// Reuse existing definitions rather than defining them again here, // Reuse existing definitions rather than defining them again here,
// otherwise they could get out of sync. // otherwise they could get out of sync.
// See https://gitlab.com/gitlab-org/gitlab-ui/issues/554. // See https://gitlab.com/gitlab-org/gitlab-ui/issues/554.
export { dateFormats as DATE_FORMATS } from 'ee/analytics/shared/constants'; export { dateFormats as DATE_FORMATS } from '~/analytics/shared/constants';
export const POLICY_KINDS = { export const POLICY_KINDS = {
ciliumNetwork: 'CiliumNetworkPolicy', ciliumNetwork: 'CiliumNetworkPolicy',
......
...@@ -69,8 +69,7 @@ module EE ...@@ -69,8 +69,7 @@ module EE
private private
def expose_mr_status_checks? def expose_mr_status_checks?
::Feature.enabled?(:ff_external_status_checks, project, default_enabled: :yaml) && current_user.present? &&
current_user.present? &&
project.external_status_checks.applicable_to_branch(merge_request.target_branch).any? project.external_status_checks.applicable_to_branch(merge_request.target_branch).any?
end end
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
= render_ce 'projects/merge_request_merge_checks_settings', project: @project, form: form = render_ce 'projects/merge_request_merge_checks_settings', project: @project, form: form
- if ::Feature.enabled?(:ff_external_status_checks, @project, default_enabled: :yaml) && @project.licensed_feature_available?(:external_status_checks) - if @project.licensed_feature_available?(:external_status_checks)
= render_if_exists 'projects/merge_request_status_checks_settings' = render_if_exists 'projects/merge_request_status_checks_settings'
= render 'projects/merge_request_merge_suggestions_settings', project: @project, form: form = render 'projects/merge_request_merge_suggestions_settings', project: @project, form: form
......
---
name: ff_external_status_checks
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54002
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/320783
milestone: '14.1'
type: development
group: group::compliance
default_enabled: false
...@@ -13,8 +13,7 @@ module API ...@@ -13,8 +13,7 @@ module API
helpers do helpers do
def check_feature_enabled! def check_feature_enabled!
unauthorized! unless user_project.licensed_feature_available?(:external_status_checks) && unauthorized! unless user_project.licensed_feature_available?(:external_status_checks)
Feature.enabled?(:ff_external_status_checks, user_project)
end end
end end
...@@ -22,7 +21,6 @@ module API ...@@ -22,7 +21,6 @@ module API
segment ':id/external_status_checks' do segment ':id/external_status_checks' do
desc 'Create a new external status check' do desc 'Create a new external status check' do
success ::API::Entities::ExternalStatusCheck success ::API::Entities::ExternalStatusCheck
detail 'This feature is gated by the :ff_external_status_checks feature flag.'
end end
params do params do
requires :name, type: String, desc: 'The name of the external status check' requires :name, type: String, desc: 'The name of the external status check'
...@@ -45,9 +43,7 @@ module API ...@@ -45,9 +43,7 @@ module API
render_api_error!(service.payload[:errors], service.http_status) render_api_error!(service.payload[:errors], service.http_status)
end end
end end
desc 'List project\'s external approval rules' do desc 'List project\'s external approval rules'
detail 'This feature is gated by the :ff_external_status_checks feature flag.'
end
params do params do
use :pagination use :pagination
end end
...@@ -60,7 +56,6 @@ module API ...@@ -60,7 +56,6 @@ module API
segment ':check_id' do segment ':check_id' do
desc 'Update an external approval rule' do desc 'Update an external approval rule' do
success ::API::Entities::ExternalStatusCheck success ::API::Entities::ExternalStatusCheck
detail 'This feature is gated by the :ff_external_status_checks feature flag.'
end end
params do params do
requires :check_id, type: Integer, desc: 'The ID of the external status check' requires :check_id, type: Integer, desc: 'The ID of the external status check'
...@@ -85,9 +80,7 @@ module API ...@@ -85,9 +80,7 @@ module API
end end
end end
desc 'Delete an external status check' do desc 'Delete an external status check'
detail 'This feature is gated by the :ff_external_status_checks feature flag.'
end
params do params do
requires :check_id, type: Integer, desc: 'The ID of the status check' requires :check_id, type: Integer, desc: 'The ID of the status check'
end end
...@@ -106,7 +99,6 @@ module API ...@@ -106,7 +99,6 @@ module API
segment ':id/merge_requests/:merge_request_iid' do segment ':id/merge_requests/:merge_request_iid' do
desc 'Externally approve a merge request' do desc 'Externally approve a merge request' do
detail 'This feature was introduced in 13.12 and is gated behind the :ff_external_status_checks feature flag.'
success Entities::MergeRequests::StatusCheckResponse success Entities::MergeRequests::StatusCheckResponse
end end
params do params do
...@@ -116,8 +108,6 @@ module API ...@@ -116,8 +108,6 @@ module API
requires :sha, type: String, desc: 'The current SHA at HEAD of the merge request.' requires :sha, type: String, desc: 'The current SHA at HEAD of the merge request.'
end end
post 'status_check_responses' do post 'status_check_responses' do
not_found! unless ::Feature.enabled?(:ff_external_status_checks, user_project)
merge_request = find_merge_request_with_access(params[:merge_request_iid], :approve_merge_request) merge_request = find_merge_request_with_access(params[:merge_request_iid], :approve_merge_request)
check_sha_param!(params, merge_request) check_sha_param!(params, merge_request)
...@@ -127,12 +117,8 @@ module API ...@@ -127,12 +117,8 @@ module API
present(approval, with: Entities::MergeRequests::StatusCheckResponse) present(approval, with: Entities::MergeRequests::StatusCheckResponse)
end end
desc 'List all status checks for a merge request and their state.' do desc 'List all status checks for a merge request and their state.'
detail 'This feature was introduced in 13.12 and is gated behind the :ff_external_status_checks feature flag.'
end
get 'status_checks' do get 'status_checks' do
not_found! unless ::Feature.enabled?(:ff_external_status_checks, user_project)
merge_request = find_merge_request_with_access(params[:merge_request_iid], :approve_merge_request) merge_request = find_merge_request_with_access(params[:merge_request_iid], :approve_merge_request)
present(paginate(user_project.external_status_checks.applicable_to_branch(merge_request.target_branch)), with: Entities::MergeRequests::StatusCheck, merge_request: merge_request, sha: merge_request.source_branch_sha) present(paginate(user_project.external_status_checks.applicable_to_branch(merge_request.target_branch)), with: Entities::MergeRequests::StatusCheck, merge_request: merge_request, sha: merge_request.source_branch_sha)
......
...@@ -29,37 +29,23 @@ RSpec.describe 'Merge request > User sees status checks widget', :js do ...@@ -29,37 +29,23 @@ RSpec.describe 'Merge request > User sees status checks widget', :js do
visit project_merge_request_path(project, merge_request) visit project_merge_request_path(project, merge_request)
end end
context 'feature flag is enabled' do it 'shows the widget' do
before do expect(page).to have_content('Status checks 1 pending')
stub_feature_flags(ff_external_status_checks: true) end
end
it 'shows the widget' do it 'shows the status check issues', :aggregate_failures do
expect(page).to have_content('Status checks 1 pending') within '[data-test-id="mr-status-checks"]' do
click_button 'Expand'
end end
it 'shows the status check issues', :aggregate_failures do [check1, check2].each do |rule|
within '[data-test-id="mr-status-checks"]' do within "[data-testid='mr-status-check-issue-#{rule.id}']" do
click_button 'Expand' icon_type = rule.approved?(merge_request, merge_request.source_branch_sha) ? 'success' : 'pending'
end expect(page).to have_css(".ci-status-icon-#{icon_type}")
expect(page).to have_content("#{rule.name}, #{rule.external_url}")
[check1, check2].each do |rule|
within "[data-testid='mr-status-check-issue-#{rule.id}']" do
icon_type = rule.approved?(merge_request, merge_request.source_branch_sha) ? 'success' : 'pending'
expect(page).to have_css(".ci-status-icon-#{icon_type}")
expect(page).to have_content("#{rule.name}, #{rule.external_url}")
end
end end
end end
end end
context 'feature flag is disabled' do
before do
stub_feature_flags(ff_external_status_checks: false)
end
it_behaves_like 'no status checks widget'
end
end end
context 'user is not logged in' do context 'user is not logged in' do
......
...@@ -27,7 +27,12 @@ exports[`MergeRequestsGrid component when drawer enabled is false when initializ ...@@ -27,7 +27,12 @@ exports[`MergeRequestsGrid component when drawer enabled is false when initializ
<div <div
data-testid="merge-request" data-testid="merge-request"
> >
Merge request 0 <a
data-testid="merge-request-link"
href=""
>
Merge request 0
</a>
</div> </div>
<status-stub <status-stub
...@@ -57,7 +62,12 @@ exports[`MergeRequestsGrid component when drawer enabled is false when initializ ...@@ -57,7 +62,12 @@ exports[`MergeRequestsGrid component when drawer enabled is false when initializ
<div <div
data-testid="merge-request" data-testid="merge-request"
> >
Merge request 1 <a
data-testid="merge-request-link"
href=""
>
Merge request 1
</a>
</div> </div>
<status-stub <status-stub
...@@ -116,14 +126,20 @@ exports[`MergeRequestsGrid component when drawer enabled is true when initialize ...@@ -116,14 +126,20 @@ exports[`MergeRequestsGrid component when drawer enabled is true when initialize
heading="Updates" heading="Updates"
/> />
<a <div
class="dashboard-merge-request dashboard-grid gl-display-grid gl-grid-tpl-rows-auto gl-hover-bg-blue-50 gl-hover-text-decoration-none gl-hover-cursor-pointer" class="dashboard-merge-request dashboard-grid gl-display-grid gl-grid-tpl-rows-auto gl-hover-bg-blue-50 gl-hover-text-decoration-none gl-hover-cursor-pointer"
data-testid="merge-request-link" data-testid="merge-request-drawer-toggle"
tabindex="0"
> >
<div <div
data-testid="merge-request" data-testid="merge-request"
> >
Merge request 0 <a
data-testid="merge-request-link"
href=""
>
Merge request 0
</a>
</div> </div>
<status-stub <status-stub
...@@ -150,15 +166,21 @@ exports[`MergeRequestsGrid component when drawer enabled is true when initialize ...@@ -150,15 +166,21 @@ exports[`MergeRequestsGrid component when drawer enabled is true when initialize
tooltipplacement="bottom" tooltipplacement="bottom"
/> />
</div> </div>
</a> </div>
<a <div
class="dashboard-merge-request dashboard-grid gl-display-grid gl-grid-tpl-rows-auto gl-hover-bg-blue-50 gl-hover-text-decoration-none gl-hover-cursor-pointer" class="dashboard-merge-request dashboard-grid gl-display-grid gl-grid-tpl-rows-auto gl-hover-bg-blue-50 gl-hover-text-decoration-none gl-hover-cursor-pointer"
data-testid="merge-request-link" data-testid="merge-request-drawer-toggle"
tabindex="0"
> >
<div <div
data-testid="merge-request" data-testid="merge-request"
> >
Merge request 1 <a
data-testid="merge-request-link"
href=""
>
Merge request 1
</a>
</div> </div>
<status-stub <status-stub
...@@ -185,7 +207,7 @@ exports[`MergeRequestsGrid component when drawer enabled is true when initialize ...@@ -185,7 +207,7 @@ exports[`MergeRequestsGrid component when drawer enabled is true when initialize
tooltipplacement="bottom" tooltipplacement="bottom"
/> />
</div> </div>
</a> </div>
</div> </div>
<pagination-stub <pagination-stub
......
...@@ -9,8 +9,10 @@ import { createMergeRequests, mergedAt } from '../../mock_data'; ...@@ -9,8 +9,10 @@ import { createMergeRequests, mergedAt } from '../../mock_data';
describe('MergeRequestsGrid component', () => { describe('MergeRequestsGrid component', () => {
let wrapper; let wrapper;
const findMergeRequestLinks = () => wrapper.findAllByTestId('merge-request-link'); const findMergeRequestDrawerToggles = () =>
wrapper.findAllByTestId('merge-request-drawer-toggle');
const findMergeRequests = () => wrapper.findAllByTestId('merge-request'); const findMergeRequests = () => wrapper.findAllByTestId('merge-request');
const findMergeRequestLinks = () => wrapper.findAllByTestId('merge-request-link');
const findTime = () => wrapper.findComponent(TimeAgoTooltip); const findTime = () => wrapper.findComponent(TimeAgoTooltip);
const findStatuses = () => wrapper.findAllComponents(Status); const findStatuses = () => wrapper.findAllComponents(Status);
const findApprovers = () => wrapper.findComponent(Approvers); const findApprovers = () => wrapper.findComponent(Approvers);
...@@ -26,7 +28,7 @@ describe('MergeRequestsGrid component', () => { ...@@ -26,7 +28,7 @@ describe('MergeRequestsGrid component', () => {
stubs: { stubs: {
MergeRequest: { MergeRequest: {
props: { mergeRequest: Object }, props: { mergeRequest: Object },
template: `<div data-testid="merge-request">{{ mergeRequest.title }}</div>`, template: `<div data-testid="merge-request"><a href="" data-testid="merge-request-link">{{ mergeRequest.title }}</a></div>`,
}, },
}, },
}); });
...@@ -116,10 +118,18 @@ describe('MergeRequestsGrid component', () => { ...@@ -116,10 +118,18 @@ describe('MergeRequestsGrid component', () => {
wrapper = createComponent(mergeRequest, true); wrapper = createComponent(mergeRequest, true);
}); });
it('toggles the drawer when a merge request link is clicked', () => { describe.each(['click', 'keypress.enter'])('when the %s event is triggered', (event) => {
findMergeRequestLinks().at(0).trigger('click'); it('toggles the drawer when a merge request drawer toggle is the target', () => {
findMergeRequestDrawerToggles().at(0).trigger(event);
expect(wrapper.emitted('toggleDrawer')[0][0]).toStrictEqual(mergeRequests[0]);
});
it('does not toggle the drawer if an inner link is the target', () => {
findMergeRequestLinks().at(0).trigger(event);
expect(wrapper.emitted('toggleDrawer')[0][0]).toStrictEqual(mergeRequests[0]); expect(wrapper.emitted('toggleDrawer')).toBe(undefined);
});
}); });
}); });
}); });
...@@ -186,15 +186,12 @@ RSpec.describe MergeRequestPresenter do ...@@ -186,15 +186,12 @@ RSpec.describe MergeRequestPresenter do
let(:exposed_path) { expose_path("/api/v4/projects/#{merge_request.project.id}/merge_requests/#{merge_request.iid}/status_checks") } let(:exposed_path) { expose_path("/api/v4/projects/#{merge_request.project.id}/merge_requests/#{merge_request.iid}/status_checks") }
where(:feature_flag_enabled?, :authenticated?, :has_status_checks?, :exposes_path?) do where(:authenticated?, :has_status_checks?, :exposes_path?) do
false | false | false | false false | false | false
false | false | true | false false | true | false
false | true | true | false true | true | true
false | true | false | false true | false | false
true | false | false | false true | true | true
true | true | false | false
true | false | true | false
true | true | true | true
end end
with_them do with_them do
...@@ -202,7 +199,6 @@ RSpec.describe MergeRequestPresenter do ...@@ -202,7 +199,6 @@ RSpec.describe MergeRequestPresenter do
let(:path) { exposes_path? ? exposed_path : nil } let(:path) { exposes_path? ? exposed_path : nil }
before do before do
stub_feature_flags(ff_external_status_checks: feature_flag_enabled?)
allow(project.external_status_checks).to receive(:applicable_to_branch).and_return([{ branch: 'foo' }]) allow(project.external_status_checks).to receive(:applicable_to_branch).and_return([{ branch: 'foo' }])
allow(project.external_status_checks.applicable_to_branch).to receive(:any?).and_return(has_status_checks?) allow(project.external_status_checks.applicable_to_branch).to receive(:any?).and_return(has_status_checks?)
end end
...@@ -210,13 +206,9 @@ RSpec.describe MergeRequestPresenter do ...@@ -210,13 +206,9 @@ RSpec.describe MergeRequestPresenter do
it { is_expected.to eq(path) } it { is_expected.to eq(path) }
end end
context 'with the feature flag enabled and user authenticated' do context 'with the user authenticated' do
let(:presenter) { described_class.new(merge_request, current_user: user) } let(:presenter) { described_class.new(merge_request, current_user: user) }
before do
stub_feature_flags(ff_external_status_checks: true)
end
context 'without applicable branches' do context 'without applicable branches' do
before do before do
create(:external_status_check, project: project, protected_branches: [create(:protected_branch, name: 'testbranch')]) create(:external_status_check, project: project, protected_branches: [create(:protected_branch, name: 'testbranch')])
......
...@@ -32,18 +32,6 @@ RSpec.describe API::StatusChecks do ...@@ -32,18 +32,6 @@ RSpec.describe API::StatusChecks do
describe 'GET :id/merge_requests/:merge_request_iid/status_checks' do describe 'GET :id/merge_requests/:merge_request_iid/status_checks' do
subject { get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/status_checks", user), params: { external_status_check_id: rule.id, sha: sha } } subject { get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/status_checks", user), params: { external_status_check_id: rule.id, sha: sha } }
context 'feature flag is disabled' do
before do
stub_feature_flags(ff_external_status_checks: false)
end
it 'returns a not found error' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when current_user has access' do context 'when current_user has access' do
before do before do
stub_licensed_features(external_status_checks: true) stub_licensed_features(external_status_checks: true)
...@@ -81,18 +69,6 @@ RSpec.describe API::StatusChecks do ...@@ -81,18 +69,6 @@ RSpec.describe API::StatusChecks do
describe 'POST :id/:merge_requests/:merge_request_iid/status_check_responses' do describe 'POST :id/:merge_requests/:merge_request_iid/status_check_responses' do
subject { post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/status_check_responses", user), params: { external_status_check_id: rule.id, sha: sha } } subject { post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/status_check_responses", user), params: { external_status_check_id: rule.id, sha: sha } }
context 'feature flag is disabled' do
before do
stub_feature_flags(ff_external_status_checks: false)
end
it 'returns a not found error' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when user has access' do context 'when user has access' do
before do before do
stub_licensed_features(external_status_checks: true) stub_licensed_features(external_status_checks: true)
...@@ -153,20 +129,15 @@ RSpec.describe API::StatusChecks do ...@@ -153,20 +129,15 @@ RSpec.describe API::StatusChecks do
end end
context 'when feature is disabled, unlicensed or user has permission' do context 'when feature is disabled, unlicensed or user has permission' do
where(:licensed, :flag, :project_owner, :status) do where(:licensed, :project_owner, :status) do
false | false | false | :not_found false | false | :not_found
false | false | true | :unauthorized false | true | :unauthorized
false | true | true | :unauthorized true | false | :not_found
false | true | false | :not_found true | true | :success
true | false | false | :not_found
true | false | true | :unauthorized
true | true | false | :not_found
true | true | true | :success
end end
with_them do with_them do
before do before do
stub_feature_flags(ff_external_status_checks: flag)
stub_licensed_features(external_status_checks: licensed) stub_licensed_features(external_status_checks: licensed)
end end
...@@ -182,7 +153,6 @@ RSpec.describe API::StatusChecks do ...@@ -182,7 +153,6 @@ RSpec.describe API::StatusChecks do
describe 'POST projects/:id/external_status_checks' do describe 'POST projects/:id/external_status_checks' do
context 'successfully creating new external approval rule' do context 'successfully creating new external approval rule' do
before do before do
stub_feature_flags(ff_external_status_checks: true)
stub_licensed_features(external_status_checks: true) stub_licensed_features(external_status_checks: true)
end end
...@@ -229,20 +199,15 @@ RSpec.describe API::StatusChecks do ...@@ -229,20 +199,15 @@ RSpec.describe API::StatusChecks do
end end
context 'when feature is disabled, unlicensed or user has permission' do context 'when feature is disabled, unlicensed or user has permission' do
where(:licensed, :flag, :project_owner, :status) do where(:licensed, :project_owner, :status) do
false | false | false | :not_found false | false | :not_found
false | false | true | :unauthorized false | true | :unauthorized
false | true | true | :unauthorized true | false | :not_found
false | true | false | :not_found true | true | :created
true | false | false | :not_found
true | false | true | :unauthorized
true | true | false | :not_found
true | true | true | :created
end end
with_them do with_them do
before do before do
stub_feature_flags(ff_external_status_checks: flag)
stub_licensed_features(external_status_checks: licensed) stub_licensed_features(external_status_checks: licensed)
end end
...@@ -280,20 +245,15 @@ RSpec.describe API::StatusChecks do ...@@ -280,20 +245,15 @@ RSpec.describe API::StatusChecks do
end end
context 'when feature is disabled, unlicensed or user has permission' do context 'when feature is disabled, unlicensed or user has permission' do
where(:licensed, :flag, :project_owner, :status) do where(:licensed, :project_owner, :status) do
false | false | false | :not_found false | false | :not_found
false | false | true | :unauthorized false | true | :unauthorized
false | true | true | :unauthorized true | false | :not_found
false | true | false | :not_found true | true | :success
true | false | false | :not_found
true | false | true | :unauthorized
true | true | false | :not_found
true | true | true | :success
end end
with_them do with_them do
before do before do
stub_feature_flags(ff_external_status_checks: flag)
stub_licensed_features(external_status_checks: licensed) stub_licensed_features(external_status_checks: licensed)
end end
...@@ -311,7 +271,6 @@ RSpec.describe API::StatusChecks do ...@@ -311,7 +271,6 @@ RSpec.describe API::StatusChecks do
context 'successfully updating external approval rule' do context 'successfully updating external approval rule' do
before do before do
stub_feature_flags(ff_external_status_checks: true)
stub_licensed_features(external_status_checks: true) stub_licensed_features(external_status_checks: true)
end end
...@@ -362,20 +321,15 @@ RSpec.describe API::StatusChecks do ...@@ -362,20 +321,15 @@ RSpec.describe API::StatusChecks do
end end
context 'when feature is disabled, unlicensed or user has permission' do context 'when feature is disabled, unlicensed or user has permission' do
where(:licensed, :flag, :project_owner, :status) do where(:licensed, :project_owner, :status) do
false | false | false | :not_found false | false | :not_found
false | false | true | :unauthorized false | true | :unauthorized
false | true | true | :unauthorized true | false | :not_found
false | true | false | :not_found true | true | :success
true | false | false | :not_found
true | false | true | :unauthorized
true | true | false | :not_found
true | true | true | :success
end end
with_them do with_them do
before do before do
stub_feature_flags(ff_external_status_checks: flag)
stub_licensed_features(external_status_checks: licensed) stub_licensed_features(external_status_checks: licensed)
end end
......
...@@ -16,9 +16,9 @@ RSpec.describe 'projects/edit' do ...@@ -16,9 +16,9 @@ RSpec.describe 'projects/edit' do
end end
context 'status checks' do context 'status checks' do
context 'feature flag is disabled' do context 'feature is not available' do
before do before do
stub_feature_flags(ff_external_status_checks: false) stub_licensed_features(external_status_checks: false)
render render
end end
...@@ -26,24 +26,6 @@ RSpec.describe 'projects/edit' do ...@@ -26,24 +26,6 @@ RSpec.describe 'projects/edit' do
it 'hides the status checks area' do it 'hides the status checks area' do
expect(rendered).not_to have_content('Status check') expect(rendered).not_to have_content('Status check')
end end
end
context 'feature flag is enabled' do
before do
stub_feature_flags(ff_external_status_checks: true)
end
context 'feature is not available' do
before do
stub_licensed_features(external_status_checks: false)
render
end
it 'hides the status checks area' do
expect(rendered).not_to have_content('Status check')
end
end
context 'feature is available' do context 'feature is available' do
before do before do
......
...@@ -4,11 +4,42 @@ module Gitlab ...@@ -4,11 +4,42 @@ module Gitlab
module Graphql module Graphql
module Pagination module Pagination
module Keyset module Keyset
# https://gitlab.com/gitlab-org/gitlab/-/issues/334973
# Use the generic keyset implementation if the given ActiveRecord scope supports it. # Use the generic keyset implementation if the given ActiveRecord scope supports it.
# Note: this module is temporary, at some point it will be merged with Keyset::Connection # Note: this module is temporary, at some point it will be merged with Keyset::Connection
module GenericKeysetPagination module GenericKeysetPagination
extend ActiveSupport::Concern extend ActiveSupport::Concern
# rubocop: disable Naming/PredicateName
# rubocop: disable CodeReuse/ActiveRecord
def has_next_page
return super unless Gitlab::Pagination::Keyset::Order.keyset_aware?(items)
strong_memoize(:generic_keyset_pagination_has_next_page) do
if before
# If `before` is specified, that points to a specific record,
# even if it's the last one. Since we're asking for `before`,
# then the specific record we're pointing to is in the
# next page
true
elsif first
case sliced_nodes
when Array
sliced_nodes.size > limit_value
else
# If we count the number of requested items plus one (`limit_value + 1`),
# then if we get `limit_value + 1` then we know there is a next page
sliced_nodes.limit(1).offset(limit_value).exists?
# replacing relation count
# relation_count(set_limit(sliced_nodes, limit_value + 1)) == limit_value + 1
end
else
false
end
end
end
# rubocop: enable CodeReuse/ActiveRecord
def ordered_items def ordered_items
raise ArgumentError, 'Relation must have a primary key' unless items.primary_key.present? raise ArgumentError, 'Relation must have a primary key' unless items.primary_key.present?
......
...@@ -355,6 +355,10 @@ RSpec.describe Gitlab::Graphql::Pagination::Keyset::Connection do ...@@ -355,6 +355,10 @@ RSpec.describe Gitlab::Graphql::Pagination::Keyset::Connection do
context 'when primary key is not in original order' do context 'when primary key is not in original order' do
let(:nodes) { Project.order(last_repository_check_at: :desc) } let(:nodes) { Project.order(last_repository_check_at: :desc) }
before do
stub_feature_flags(new_graphql_keyset_pagination: false)
end
it 'is added to end' do it 'is added to end' do
sliced = subject.sliced_nodes sliced = subject.sliced_nodes
......
...@@ -4524,10 +4524,10 @@ domhandler@^4.0.0, domhandler@^4.2.0: ...@@ -4524,10 +4524,10 @@ domhandler@^4.0.0, domhandler@^4.2.0:
dependencies: dependencies:
domelementtype "^2.2.0" domelementtype "^2.2.0"
dompurify@^2.2.9: dompurify@^2.2.9, dompurify@^2.3.0:
version "2.2.9" version "2.3.0"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.2.9.tgz#4b42e244238032d9286a0d2c87b51313581d9624" resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.0.tgz#07bb39515e491588e5756b1d3e8375b5964814e2"
integrity sha512-+9MqacuigMIZ+1+EwoEltogyWGFTJZWU3258Rupxs+2CGs4H914G9er6pZbsme/bvb5L67o2rade9n21e4RW/w== integrity sha512-VV5C6Kr53YVHGOBKO/F86OYX6/iLTw2yVSI721gKetxpHCK/V5TaLEf9ODjRgl1KLSWRMY6cUhAbv/c+IUnwQw==
domutils@^1.5.1: domutils@^1.5.1:
version "1.7.0" version "1.7.0"
......
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