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 OFFSET_DATE_BY_ONE = 1;
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
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)
offset_pagination(service.execute)
service.execute
end
# https://gitlab.com/gitlab-org/gitlab/-/issues/235681
......
......@@ -332,12 +332,15 @@ module Issuable
# 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.
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(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))
end
......@@ -384,7 +387,7 @@ module Issuable
if use_cte
attribute_names.map { |attr| arel_table[attr.to_sym] }
else
arel_table[:id]
[arel_table[:id]]
end
end
......
......@@ -46,7 +46,7 @@ module Sortable
private
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
.joins(:label_links)
.where("label_priorities.project_id = #{project_column}")
......
......@@ -268,10 +268,41 @@ class Issue < ApplicationRecord
# `with_cte` argument allows sorting when using CTE queries and prevents
# errors in postgres when using CTE search optimisation
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)
.reorder(Gitlab::Database.nulls_last_order('relative_position', 'ASC'),
Gitlab::Database.nulls_last_order('highest_priority', 'ASC'),
"id DESC")
.reorder(order)
end
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
# Temporary disable moving null elements because of performance problems
......
......@@ -7,14 +7,8 @@ type: reference, api
# External Status Checks API **(ULTIMATE)**
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3869) in GitLab 14.0.
> - It's [deployed behind a feature flag](../user/feature_flags.md), disabled by default.
> - It's disabled on GitLab.com.
> - It's not recommended for production use.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-external-status-checks).
WARNING:
This feature might not be available to you. Check the **version history** note above for details.
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3869) in GitLab 14.0, disabled behind the `:ff_external_status_checks` feature flag.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/320783) in GitLab 14.1.
## List status checks for a merge request
......@@ -151,35 +145,6 @@ PUT /projects/:id/external_status_checks/:check_id
| `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 |
## 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
- [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
# External Status Checks **(ULTIMATE)**
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3869) in GitLab 14.0.
> - It's [deployed behind a feature flag](../../feature_flags.md), disabled by default.
> - It's disabled on GitLab.com.
> - It's not recommended for production use.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-status-checks). **(ULTIMATE SELF)**
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3869) in GitLab 14.0, disabled behind the `:ff_external_status_checks` feature flag.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/320783) in GitLab 14.1.
WARNING:
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.
## Status checks widget
> - [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
status checks:
......@@ -188,31 +184,6 @@ You should:
- Check the [GitLab status page](https://status.gitlab.com/) if the problem persists,
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
- [External status checks API](../../../api/status_checks.md)
<script>
import { GlAlert } from '@gitlab/ui';
import { mapActions, mapState, mapGetters } from 'vuex';
import { dateFormats } from '~/analytics/shared/constants';
import { __ } from '~/locale';
import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
import Scatterplot from '../../shared/components/scatterplot.vue';
import { dateFormats } from '../../shared/constants';
import StageDropdownFilter from './stage_dropdown_filter.vue';
export default {
......
import dateFormat from 'dateformat';
import { isNumber } from 'lodash';
import { dateFormats } from '~/analytics/shared/constants';
import { OVERVIEW_STAGE_ID } from '~/cycle_analytics/constants';
import { pathNavigationData as basePathNavigationData } from '~/cycle_analytics/store/getters';
import { filterStagesByHiddenStatus } from '~/cycle_analytics/utils';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import httpStatus from '~/lib/utils/http_status';
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';
export const hasNoAccessError = (state) => state.errorCode === httpStatus.FORBIDDEN;
......
import dateFormat from 'dateformat';
import { isNumber } from 'lodash';
import { dateFormats } from '~/analytics/shared/constants';
import { OVERVIEW_STAGE_ID } from '~/cycle_analytics/constants';
import { medianTimeToParsedSeconds } from '~/cycle_analytics/utils';
import createFlash, { hideFlash } from '~/flash';
......@@ -7,7 +8,6 @@ import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { newDate, dayAfter, secondsToDays, getDatesInRange } from '~/lib/utils/datetime_utility';
import httpStatus from '~/lib/utils/http_status';
import { convertToSnakeCase, slugify } from '~/lib/utils/text_utility';
import { dateFormats } from '../shared/constants';
import { toYmd } from '../shared/utils';
const EVENT_TYPE_LABEL = 'label';
......
<script>
import dateFormat from 'dateformat';
import DateRange from '~/analytics/shared/components/daterange.vue';
import { dateFormats } from '~/analytics/shared/constants';
import UrlSync from '~/vue_shared/components/url_sync.vue';
import { dateFormats } from '../../shared/constants';
import { DEFAULT_NUMBER_OF_DAYS } from '../constants';
import FilterBar from './filter_bar.vue';
import ThroughputChart from './throughput_chart.vue';
......
......@@ -13,10 +13,10 @@ import {
} from '@gitlab/ui';
import dateFormat from 'dateformat';
import { mapState } from 'vuex';
import { dateFormats } from '~/analytics/shared/constants';
import { approximateDuration, differenceInSeconds } from '~/lib/utils/datetime_utility';
import { s__, n__ } from '~/locale';
import { filterToQueryObject } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
import { dateFormats } from '../../shared/constants';
import {
THROUGHPUT_TABLE_STRINGS,
MERGE_REQUEST_ID_PREFIX,
......
import dateFormat from 'dateformat';
import { dateFormats } from '~/analytics/shared/constants';
import {
getMonthNames,
getDateInPast,
getDayDifference,
secondsToDays,
} from '~/lib/utils/datetime_utility';
import { dateFormats } from '../shared/constants';
import { THROUGHPUT_CHART_STRINGS, DEFAULT_NUMBER_OF_DAYS, UNITS } from './constants';
/**
......
......@@ -12,10 +12,10 @@ import {
import { GlColumnChart } from '@gitlab/ui/dist/charts';
import dateFormat from 'dateformat';
import { mapState, mapActions, mapGetters } from 'vuex';
import { dateFormats } from '~/analytics/shared/constants';
import { beginOfDayTime, endOfDayTime } from '~/lib/utils/datetime_utility';
import featureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import Scatterplot from '../../shared/components/scatterplot.vue';
import { dateFormats } from '../../shared/constants';
import urlSyncMixin from '../../shared/mixins/url_sync_mixin';
import { chartKeys } from '../constants';
import MetricChart from './metric_chart.vue';
......
import dateFormat from 'dateformat';
import { dateFormats } from '~/analytics/shared/constants';
import { getDateInPast, beginOfDayTime, endOfDayTime } from '~/lib/utils/datetime_utility';
import { dateFormats } from '../../../../shared/constants';
import { chartKeys, scatterPlotAddonQueryDays } from '../../../constants';
/**
......
import dateFormat from 'dateformat';
import { flatten } from 'lodash';
import { dateFormats } from '~/analytics/shared/constants';
import { getDayDifference, getDateInPast } from '~/lib/utils/datetime_utility';
import { median } from '~/lib/utils/number_utils';
import { dateFormats } from '../shared/constants';
/**
* Gets the labels endpoint for a given group or project
......
<script>
import { GlDiscreteScatterChart } from '@gitlab/ui/dist/charts';
import dateFormat from 'dateformat';
import { scatterChartLineProps, dateFormats } from '../constants';
import { dateFormats } from '~/analytics/shared/constants';
import { scatterChartLineProps } from '../constants';
export default {
components: {
......
import { masks } from 'dateformat';
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 = {
default: {
type: 'line',
......
import dateFormat from 'dateformat';
import { dateFormats } from '~/analytics/shared/constants';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
import { dateFormats } from './constants';
export const toYmd = (date) => dateFormat(date, dateFormats.isoDate);
......
......@@ -45,6 +45,14 @@ export default {
hasBranchDetails(mergeRequest) {
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: {
approvalStatusLabel: __('Approval Status'),
......@@ -72,12 +80,14 @@ export default {
<!-- TODO: Remove the if/else and duplicate components with https://gitlab.com/gitlab-org/gitlab/-/issues/334682 -->
<template v-if="drawerEnabled">
<a
<div
v-for="mergeRequest in mergeRequests"
: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"
data-testid="merge-request-link"
@click="$emit('toggleDrawer', mergeRequest)"
data-testid="merge-request-drawer-toggle"
tabindex="0"
@click="onRowClick($event, mergeRequest)"
@keypress.enter="onRowClick($event, mergeRequest)"
>
<merge-request
:key="key(mergeRequest.id, $options.keyTypes.mergeRequest)"
......@@ -123,7 +133,7 @@ export default {
</template>
</time-ago-tooltip>
</div>
</a>
</div>
</template>
<template v-else>
<template v-for="mergeRequest in mergeRequests">
......
......@@ -19,7 +19,7 @@ export const COLORS = {
// Reuse existing definitions rather than defining them again here,
// otherwise they could get out of sync.
// 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 = {
ciliumNetwork: 'CiliumNetworkPolicy',
......
......@@ -69,7 +69,6 @@ module EE
private
def expose_mr_status_checks?
::Feature.enabled?(:ff_external_status_checks, project, default_enabled: :yaml) &&
current_user.present? &&
project.external_status_checks.applicable_to_branch(merge_request.target_branch).any?
end
......
......@@ -8,7 +8,7 @@
= 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 '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
helpers do
def check_feature_enabled!
unauthorized! unless user_project.licensed_feature_available?(:external_status_checks) &&
Feature.enabled?(:ff_external_status_checks, user_project)
unauthorized! unless user_project.licensed_feature_available?(:external_status_checks)
end
end
......@@ -22,7 +21,6 @@ module API
segment ':id/external_status_checks' do
desc 'Create a new external status check' do
success ::API::Entities::ExternalStatusCheck
detail 'This feature is gated by the :ff_external_status_checks feature flag.'
end
params do
requires :name, type: String, desc: 'The name of the external status check'
......@@ -45,9 +43,7 @@ module API
render_api_error!(service.payload[:errors], service.http_status)
end
end
desc 'List project\'s external approval rules' do
detail 'This feature is gated by the :ff_external_status_checks feature flag.'
end
desc 'List project\'s external approval rules'
params do
use :pagination
end
......@@ -60,7 +56,6 @@ module API
segment ':check_id' do
desc 'Update an external approval rule' do
success ::API::Entities::ExternalStatusCheck
detail 'This feature is gated by the :ff_external_status_checks feature flag.'
end
params do
requires :check_id, type: Integer, desc: 'The ID of the external status check'
......@@ -85,9 +80,7 @@ module API
end
end
desc 'Delete an external status check' do
detail 'This feature is gated by the :ff_external_status_checks feature flag.'
end
desc 'Delete an external status check'
params do
requires :check_id, type: Integer, desc: 'The ID of the status check'
end
......@@ -106,7 +99,6 @@ module API
segment ':id/merge_requests/:merge_request_iid' 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
end
params do
......@@ -116,8 +108,6 @@ module API
requires :sha, type: String, desc: 'The current SHA at HEAD of the merge request.'
end
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)
check_sha_param!(params, merge_request)
......@@ -127,12 +117,8 @@ module API
present(approval, with: Entities::MergeRequests::StatusCheckResponse)
end
desc 'List all status checks for a merge request and their state.' do
detail 'This feature was introduced in 13.12 and is gated behind the :ff_external_status_checks feature flag.'
end
desc 'List all status checks for a merge request and their state.'
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)
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,11 +29,6 @@ RSpec.describe 'Merge request > User sees status checks widget', :js do
visit project_merge_request_path(project, merge_request)
end
context 'feature flag is enabled' do
before do
stub_feature_flags(ff_external_status_checks: true)
end
it 'shows the widget' do
expect(page).to have_content('Status checks 1 pending')
end
......@@ -53,15 +48,6 @@ RSpec.describe 'Merge request > User sees status checks widget', :js do
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
context 'user is not logged in' do
before do
visit project_merge_request_path(project, merge_request)
......
......@@ -26,8 +26,13 @@ exports[`MergeRequestsGrid component when drawer enabled is false when initializ
<div
data-testid="merge-request"
>
<a
data-testid="merge-request-link"
href=""
>
Merge request 0
</a>
</div>
<status-stub
......@@ -56,8 +61,13 @@ exports[`MergeRequestsGrid component when drawer enabled is false when initializ
</div>
<div
data-testid="merge-request"
>
<a
data-testid="merge-request-link"
href=""
>
Merge request 1
</a>
</div>
<status-stub
......@@ -116,14 +126,20 @@ exports[`MergeRequestsGrid component when drawer enabled is true when initialize
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"
data-testid="merge-request-link"
data-testid="merge-request-drawer-toggle"
tabindex="0"
>
<div
data-testid="merge-request"
>
<a
data-testid="merge-request-link"
href=""
>
Merge request 0
</a>
</div>
<status-stub
......@@ -150,15 +166,21 @@ exports[`MergeRequestsGrid component when drawer enabled is true when initialize
tooltipplacement="bottom"
/>
</div>
</a>
<a
</div>
<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"
data-testid="merge-request-link"
data-testid="merge-request-drawer-toggle"
tabindex="0"
>
<div
data-testid="merge-request"
>
<a
data-testid="merge-request-link"
href=""
>
Merge request 1
</a>
</div>
<status-stub
......@@ -185,7 +207,7 @@ exports[`MergeRequestsGrid component when drawer enabled is true when initialize
tooltipplacement="bottom"
/>
</div>
</a>
</div>
</div>
<pagination-stub
......
......@@ -9,8 +9,10 @@ import { createMergeRequests, mergedAt } from '../../mock_data';
describe('MergeRequestsGrid component', () => {
let wrapper;
const findMergeRequestLinks = () => wrapper.findAllByTestId('merge-request-link');
const findMergeRequestDrawerToggles = () =>
wrapper.findAllByTestId('merge-request-drawer-toggle');
const findMergeRequests = () => wrapper.findAllByTestId('merge-request');
const findMergeRequestLinks = () => wrapper.findAllByTestId('merge-request-link');
const findTime = () => wrapper.findComponent(TimeAgoTooltip);
const findStatuses = () => wrapper.findAllComponents(Status);
const findApprovers = () => wrapper.findComponent(Approvers);
......@@ -26,7 +28,7 @@ describe('MergeRequestsGrid component', () => {
stubs: {
MergeRequest: {
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', () => {
wrapper = createComponent(mergeRequest, true);
});
it('toggles the drawer when a merge request link is clicked', () => {
findMergeRequestLinks().at(0).trigger('click');
describe.each(['click', 'keypress.enter'])('when the %s event is triggered', (event) => {
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')).toBe(undefined);
});
});
});
});
......@@ -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") }
where(:feature_flag_enabled?, :authenticated?, :has_status_checks?, :exposes_path?) do
false | false | false | false
false | false | true | false
false | true | true | false
false | true | false | false
true | false | false | false
true | true | false | false
true | false | true | false
true | true | true | true
where(:authenticated?, :has_status_checks?, :exposes_path?) do
false | false | false
false | true | false
true | true | true
true | false | false
true | true | true
end
with_them do
......@@ -202,7 +199,6 @@ RSpec.describe MergeRequestPresenter do
let(:path) { exposes_path? ? exposed_path : nil }
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.applicable_to_branch).to receive(:any?).and_return(has_status_checks?)
end
......@@ -210,13 +206,9 @@ RSpec.describe MergeRequestPresenter do
it { is_expected.to eq(path) }
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) }
before do
stub_feature_flags(ff_external_status_checks: true)
end
context 'without applicable branches' do
before do
create(:external_status_check, project: project, protected_branches: [create(:protected_branch, name: 'testbranch')])
......
......@@ -32,18 +32,6 @@ RSpec.describe API::StatusChecks 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 } }
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
before do
stub_licensed_features(external_status_checks: true)
......@@ -81,18 +69,6 @@ RSpec.describe API::StatusChecks 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 } }
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
before do
stub_licensed_features(external_status_checks: true)
......@@ -153,20 +129,15 @@ RSpec.describe API::StatusChecks do
end
context 'when feature is disabled, unlicensed or user has permission' do
where(:licensed, :flag, :project_owner, :status) do
false | false | false | :not_found
false | false | true | :unauthorized
false | true | true | :unauthorized
false | true | false | :not_found
true | false | false | :not_found
true | false | true | :unauthorized
true | true | false | :not_found
true | true | true | :success
where(:licensed, :project_owner, :status) do
false | false | :not_found
false | true | :unauthorized
true | false | :not_found
true | true | :success
end
with_them do
before do
stub_feature_flags(ff_external_status_checks: flag)
stub_licensed_features(external_status_checks: licensed)
end
......@@ -182,7 +153,6 @@ RSpec.describe API::StatusChecks do
describe 'POST projects/:id/external_status_checks' do
context 'successfully creating new external approval rule' do
before do
stub_feature_flags(ff_external_status_checks: true)
stub_licensed_features(external_status_checks: true)
end
......@@ -229,20 +199,15 @@ RSpec.describe API::StatusChecks do
end
context 'when feature is disabled, unlicensed or user has permission' do
where(:licensed, :flag, :project_owner, :status) do
false | false | false | :not_found
false | false | true | :unauthorized
false | true | true | :unauthorized
false | true | false | :not_found
true | false | false | :not_found
true | false | true | :unauthorized
true | true | false | :not_found
true | true | true | :created
where(:licensed, :project_owner, :status) do
false | false | :not_found
false | true | :unauthorized
true | false | :not_found
true | true | :created
end
with_them do
before do
stub_feature_flags(ff_external_status_checks: flag)
stub_licensed_features(external_status_checks: licensed)
end
......@@ -280,20 +245,15 @@ RSpec.describe API::StatusChecks do
end
context 'when feature is disabled, unlicensed or user has permission' do
where(:licensed, :flag, :project_owner, :status) do
false | false | false | :not_found
false | false | true | :unauthorized
false | true | true | :unauthorized
false | true | false | :not_found
true | false | false | :not_found
true | false | true | :unauthorized
true | true | false | :not_found
true | true | true | :success
where(:licensed, :project_owner, :status) do
false | false | :not_found
false | true | :unauthorized
true | false | :not_found
true | true | :success
end
with_them do
before do
stub_feature_flags(ff_external_status_checks: flag)
stub_licensed_features(external_status_checks: licensed)
end
......@@ -311,7 +271,6 @@ RSpec.describe API::StatusChecks do
context 'successfully updating external approval rule' do
before do
stub_feature_flags(ff_external_status_checks: true)
stub_licensed_features(external_status_checks: true)
end
......@@ -362,20 +321,15 @@ RSpec.describe API::StatusChecks do
end
context 'when feature is disabled, unlicensed or user has permission' do
where(:licensed, :flag, :project_owner, :status) do
false | false | false | :not_found
false | false | true | :unauthorized
false | true | true | :unauthorized
false | true | false | :not_found
true | false | false | :not_found
true | false | true | :unauthorized
true | true | false | :not_found
true | true | true | :success
where(:licensed, :project_owner, :status) do
false | false | :not_found
false | true | :unauthorized
true | false | :not_found
true | true | :success
end
with_them do
before do
stub_feature_flags(ff_external_status_checks: flag)
stub_licensed_features(external_status_checks: licensed)
end
......
......@@ -16,23 +16,6 @@ RSpec.describe 'projects/edit' do
end
context 'status checks' do
context 'feature flag is disabled' do
before do
stub_feature_flags(ff_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 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)
......@@ -43,7 +26,6 @@ RSpec.describe 'projects/edit' do
it 'hides the status checks area' do
expect(rendered).not_to have_content('Status check')
end
end
context 'feature is available' do
before do
......
......@@ -4,11 +4,42 @@ module Gitlab
module Graphql
module Pagination
module Keyset
# https://gitlab.com/gitlab-org/gitlab/-/issues/334973
# 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
module GenericKeysetPagination
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
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
context 'when primary key is not in original order' do
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
sliced = subject.sliced_nodes
......
......@@ -4524,10 +4524,10 @@ domhandler@^4.0.0, domhandler@^4.2.0:
dependencies:
domelementtype "^2.2.0"
dompurify@^2.2.9:
version "2.2.9"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.2.9.tgz#4b42e244238032d9286a0d2c87b51313581d9624"
integrity sha512-+9MqacuigMIZ+1+EwoEltogyWGFTJZWU3258Rupxs+2CGs4H914G9er6pZbsme/bvb5L67o2rade9n21e4RW/w==
dompurify@^2.2.9, dompurify@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.0.tgz#07bb39515e491588e5756b1d3e8375b5964814e2"
integrity sha512-VV5C6Kr53YVHGOBKO/F86OYX6/iLTw2yVSI721gKetxpHCK/V5TaLEf9ODjRgl1KLSWRMY6cUhAbv/c+IUnwQw==
domutils@^1.5.1:
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