Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-06-01 09:09:36 +00:00
parent b7ff336e24
commit 8ef0366928
19 changed files with 175 additions and 77 deletions

View File

@ -239,7 +239,7 @@ export default {
data-testid="count-loading-icon"
/>
<span v-if="showingAllItems">{{ showingAllItemsText }}</span>
<gl-intersection-observer v-else @update="onReachingListBottom">
<gl-intersection-observer v-else @appear="onReachingListBottom">
<span>{{ paginatedIssueText }}</span>
</gl-intersection-observer>
</li>

View File

@ -1,16 +1,12 @@
<script>
import { GlNav, GlNavItemDropdown, GlDropdownForm, GlTooltip } from '@gitlab/ui';
import { s__ } from '~/locale';
import { GlNav, GlNavItemDropdown, GlDropdownForm } from '@gitlab/ui';
import TopNavDropdownMenu from './top_nav_dropdown_menu.vue';
const TOOLTIP = s__('TopNav|Switch to...');
export default {
components: {
GlNav,
GlNavItemDropdown,
GlDropdownForm,
GlTooltip,
TopNavDropdownMenu,
},
props: {
@ -19,15 +15,6 @@ export default {
required: true,
},
},
methods: {
findTooltipTarget() {
// ### Why use a target function instead of `v-gl-tooltip`?
// To get the tooltip to align correctly, we need it to target the actual
// toggle button which we don't directly render.
return this.$el.querySelector('.js-top-nav-dropdown-toggle');
},
},
TOOLTIP,
};
</script>
@ -48,12 +35,5 @@ export default {
/>
</gl-dropdown-form>
</gl-nav-item-dropdown>
<gl-tooltip
boundary="window"
:boundary-padding="0"
:target="findTooltipTarget"
placement="right"
:title="$options.TOOLTIP"
/>
</gl-nav>
</template>

View File

@ -24,7 +24,11 @@ class FeatureFlagsFinder
private
def feature_flags
project.operations_feature_flags
if exclude_legacy_flags?
project.operations_feature_flags.new_version_only
else
project.operations_feature_flags
end
end
def by_scope(items)
@ -37,4 +41,9 @@ class FeatureFlagsFinder
items
end
end
def exclude_legacy_flags?
Feature.enabled?(:remove_legacy_flags, project, default_enabled: :yaml) &&
Feature.disabled?(:remove_legacy_flags_override, project, default_enabled: :yaml)
end
end

View File

@ -49,6 +49,8 @@ module Operations
scope :enabled, -> { where(active: true) }
scope :disabled, -> { where(active: false) }
scope :new_version_only, -> { where(version: :new_version_flag)}
enum version: {
legacy_flag: 1,
new_version_flag: 2

View File

@ -0,0 +1,8 @@
---
name: remove_legacy_flags
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/62484
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/332243
milestone: '14.0'
type: development
group: group::release
default_enabled: false

View File

@ -0,0 +1,8 @@
---
name: remove_legacy_flags_override
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/62484
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/332243
milestone: '14.0'
type: development
group: group::release
default_enabled: false

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
class DropDevopsAdoptionNamespaceUniqueness < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
INDEX_NAME = 'index_analytics_devops_adoption_segments_on_namespace_id'
NEW_INDEX_NAME = 'idx_analytics_devops_adoption_segments_on_namespace_id'
def up
add_concurrent_index :analytics_devops_adoption_segments, :namespace_id, name: NEW_INDEX_NAME
remove_concurrent_index_by_name :analytics_devops_adoption_segments, INDEX_NAME
end
def down
# Clean up duplicated records
execute "DELETE FROM analytics_devops_adoption_segments WHERE id NOT IN (SELECT MIN(id) FROM analytics_devops_adoption_segments GROUP BY namespace_id)"
add_concurrent_index :analytics_devops_adoption_segments, :namespace_id, name: INDEX_NAME, unique: true
remove_concurrent_index_by_name :analytics_devops_adoption_segments, NEW_INDEX_NAME
end
end

View File

@ -0,0 +1 @@
ecef2157c20804acbad9d74df27febcf935f7f36920946fac211f3ef8b419f26

View File

@ -22273,6 +22273,8 @@ CREATE INDEX finding_evidences_on_vulnerability_occurrence_id ON vulnerability_f
CREATE INDEX finding_links_on_vulnerability_occurrence_id ON vulnerability_finding_links USING btree (vulnerability_occurrence_id);
CREATE INDEX idx_analytics_devops_adoption_segments_on_namespace_id ON analytics_devops_adoption_segments USING btree (namespace_id);
CREATE INDEX idx_audit_events_part_on_entity_id_desc_author_id_created_at ON ONLY audit_events USING btree (entity_id, entity_type, id DESC, author_id, created_at);
CREATE INDEX idx_award_emoji_on_user_emoji_name_awardable_type_awardable_id ON award_emoji USING btree (user_id, name, awardable_type, awardable_id);
@ -22459,8 +22461,6 @@ CREATE UNIQUE INDEX index_analytics_ca_project_value_streams_on_project_id_and_n
CREATE INDEX index_analytics_cycle_analytics_group_stages_custom_only ON analytics_cycle_analytics_group_stages USING btree (id) WHERE (custom = true);
CREATE UNIQUE INDEX index_analytics_devops_adoption_segments_on_namespace_id ON analytics_devops_adoption_segments USING btree (namespace_id);
CREATE INDEX index_application_settings_on_custom_project_templates_group_id ON application_settings USING btree (custom_project_templates_group_id);
CREATE INDEX index_application_settings_on_file_template_project_id ON application_settings USING btree (file_template_project_id);

View File

@ -97,8 +97,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="querydevopsadoptionsegmentsdirectdescendantsonly"></a>`directDescendantsOnly` | [`Boolean`](#boolean) | Limits segments to direct descendants of specified parent. |
| <a id="querydevopsadoptionsegmentsparentnamespaceid"></a>`parentNamespaceId` | [`NamespaceID`](#namespaceid) | Filter by ancestor namespace. |
| <a id="querydevopsadoptionsegmentsdisplaynamespaceid"></a>`displayNamespaceId` | [`NamespaceID`](#namespaceid) | Filter by display namespace. |
### `Query.echo`
@ -770,6 +769,7 @@ Input type: `BulkFindOrCreateDevopsAdoptionSegmentsInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationbulkfindorcreatedevopsadoptionsegmentsclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationbulkfindorcreatedevopsadoptionsegmentsdisplaynamespaceid"></a>`displayNamespaceId` | [`NamespaceID`](#namespaceid) | Display namespace ID. |
| <a id="mutationbulkfindorcreatedevopsadoptionsegmentsnamespaceids"></a>`namespaceIds` | [`[NamespaceID!]!`](#namespaceid) | List of Namespace IDs for the segments. |
#### Fields
@ -1105,6 +1105,7 @@ Input type: `CreateDevopsAdoptionSegmentInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationcreatedevopsadoptionsegmentclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationcreatedevopsadoptionsegmentdisplaynamespaceid"></a>`displayNamespaceId` | [`NamespaceID`](#namespaceid) | Display namespace ID. |
| <a id="mutationcreatedevopsadoptionsegmentnamespaceid"></a>`namespaceId` | [`NamespaceID!`](#namespaceid) | Namespace ID to set for the segment. |
#### Fields
@ -8276,6 +8277,7 @@ Segment.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="devopsadoptionsegmentdisplaynamespace"></a>`displayNamespace` | [`Namespace`](#namespace) | Namespace where data should be displayed. |
| <a id="devopsadoptionsegmentid"></a>`id` | [`ID!`](#id) | ID of the segment. |
| <a id="devopsadoptionsegmentlatestsnapshot"></a>`latestSnapshot` | [`DevopsAdoptionSnapshot`](#devopsadoptionsnapshot) | The latest adoption metrics for the segment. |
| <a id="devopsadoptionsegmentnamespace"></a>`namespace` | [`Namespace`](#namespace) | Namespace which should be calculated. |

View File

@ -213,9 +213,6 @@ actors.
Feature.enabled?(:some_feature, group)
```
**Percentage of time** rollout is not a good idea if what you want is to make sure a feature
is always on or off to the users. In that case, **Percentage of actors** rollout is a better method.
Lastly, to verify that the feature is deemed stable in as many cases as possible,
you should fully roll out the feature by enabling the flag **globally** by running:
@ -226,6 +223,15 @@ you should fully roll out the feature by enabling the flag **globally** by runni
This changes the feature flag state to be **enabled** always, which overrides the
existing gates (e.g. `--group=gitlab-org`) in the above processes.
##### Percentage of actors vs percentage of time rollouts
If you want to make sure a feature is always on or off for users, use a **Percentage of actors**
rollout. Avoid using percentage of _time_ rollouts in this case.
A percentage of _time_ rollout can introduce inconsistent behavior when `Feature.enabled?`
is used multiple times in the code because the feature flag value is randomized each time
`Feature.enabled?` is called on your code path.
##### Disabling feature flags
To disable a feature flag that has been globally enabled you can run:

View File

@ -69,11 +69,22 @@ module API
def feature_flags
return [] unless unleash_app_name.present?
legacy_flags = Operations::FeatureFlagScope.for_unleash_client(project, unleash_app_name)
legacy_flags =
if exclude_legacy_flags?
[]
else
Operations::FeatureFlagScope.for_unleash_client(project, unleash_app_name)
end
new_version_flags = Operations::FeatureFlag.for_unleash_client(project, unleash_app_name)
legacy_flags + new_version_flags
end
def exclude_legacy_flags?
Feature.enabled?(:remove_legacy_flags, project, default_enabled: :yaml) &&
Feature.disabled?(:remove_legacy_flags_override, project, default_enabled: :yaml)
end
end
end
end

View File

@ -32,6 +32,10 @@ module Gitlab
end
end
def delete_export?
false
end
private
def send_file

View File

@ -34330,9 +34330,6 @@ msgstr ""
msgid "Too many projects enabled. You will need to manage them via the console or the API."
msgstr ""
msgid "TopNav|Switch to..."
msgstr ""
msgid "Topics (optional)"
msgstr ""

View File

@ -147,14 +147,23 @@ RSpec.describe 'Project issue boards', :js do
end
it 'infinite scrolls list' do
create_list(:labeled_issue, 50, project: project, labels: [planning])
create_list(:labeled_issue, 30, project: project, labels: [planning])
visit_project_board_path_without_query_limit(project, board)
page.within(find('.board:nth-child(2)')) do
expect(page.find('.board-header')).to have_content('58')
expect(page.find('.board-header')).to have_content('38')
expect(page).to have_selector('.board-card', count: 10)
expect(page).to have_content('Showing 10 of 38 issues')
find('.board .board-list')
inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do
evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight")
end
expect(page).to have_selector('.board-card', count: 20)
expect(page).to have_content('Showing 20 of 58 issues')
expect(page).to have_content('Showing 20 of 38 issues')
find('.board .board-list')
@ -162,8 +171,8 @@ RSpec.describe 'Project issue boards', :js do
evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight")
end
expect(page).to have_selector('.board-card', count: 40)
expect(page).to have_content('Showing 40 of 58 issues')
expect(page).to have_selector('.board-card', count: 30)
expect(page).to have_content('Showing 30 of 38 issues')
find('.board .board-list')
@ -171,7 +180,7 @@ RSpec.describe 'Project issue boards', :js do
evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight")
end
expect(page).to have_selector('.board-card', count: 58)
expect(page).to have_selector('.board-card', count: 38)
expect(page).to have_content('Showing all issues')
end
end
@ -464,7 +473,7 @@ RSpec.describe 'Project issue boards', :js do
end
it 'infinite scrolls list with label filter' do
create_list(:labeled_issue, 50, project: project, labels: [planning, testing])
create_list(:labeled_issue, 30, project: project, labels: [planning, testing])
set_filter("label", testing.title)
click_filter_link(testing.title)
@ -475,9 +484,18 @@ RSpec.describe 'Project issue boards', :js do
wait_for_requests
page.within(find('.board:nth-child(2)')) do
expect(page.find('.board-header')).to have_content('51')
expect(page.find('.board-header')).to have_content('31')
expect(page).to have_selector('.board-card', count: 10)
expect(page).to have_content('Showing 10 of 31 issues')
find('.board .board-list')
inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do
evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight")
end
expect(page).to have_selector('.board-card', count: 20)
expect(page).to have_content('Showing 20 of 51 issues')
expect(page).to have_content('Showing 20 of 31 issues')
find('.board .board-list')
@ -485,15 +503,15 @@ RSpec.describe 'Project issue boards', :js do
evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight")
end
expect(page).to have_selector('.board-card', count: 40)
expect(page).to have_content('Showing 40 of 51 issues')
expect(page).to have_selector('.board-card', count: 30)
expect(page).to have_content('Showing 30 of 31 issues')
find('.board .board-list')
inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do
evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight")
end
expect(page).to have_selector('.board-card', count: 51)
expect(page).to have_selector('.board-card', count: 31)
expect(page).to have_content('Showing all issues')
end
end

View File

@ -24,6 +24,10 @@ RSpec.describe FeatureFlagsFinder do
let!(:feature_flag_2) { create(:operations_feature_flag, name: 'flag-b', project: project) }
let(:args) { {} }
before do
stub_feature_flags(remove_legacy_flags: false)
end
it 'returns feature flags ordered by name' do
is_expected.to eq([feature_flag_1, feature_flag_2])
end
@ -79,6 +83,16 @@ RSpec.describe FeatureFlagsFinder do
it 'returns new and legacy flags' do
is_expected.to eq([feature_flag_1, feature_flag_2, feature_flag_3])
end
context 'when legacy flags are disabled' do
before do
stub_feature_flags(remove_legacy_flags_override: false, remove_legacy_flags: true)
end
it 'returns only new flags' do
is_expected.to eq([feature_flag_3])
end
end
end
end
end

View File

@ -1,5 +1,5 @@
import { GlNavItemDropdown, GlTooltip } from '@gitlab/ui';
import { shallowMount, mount } from '@vue/test-utils';
import { GlNavItemDropdown } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import TopNavApp from '~/nav/components/top_nav_app.vue';
import TopNavDropdownMenu from '~/nav/components/top_nav_dropdown_menu.vue';
import { TEST_NAV_DATA } from '../mock_data';
@ -7,8 +7,8 @@ import { TEST_NAV_DATA } from '../mock_data';
describe('~/nav/components/top_nav_app.vue', () => {
let wrapper;
const createComponent = (mountFn = shallowMount) => {
wrapper = mountFn(TopNavApp, {
const createComponent = () => {
wrapper = shallowMount(TopNavApp, {
propsData: {
navData: TEST_NAV_DATA,
},
@ -17,7 +17,6 @@ describe('~/nav/components/top_nav_app.vue', () => {
const findNavItemDropdown = () => wrapper.findComponent(GlNavItemDropdown);
const findMenu = () => wrapper.findComponent(TopNavDropdownMenu);
const findTooltip = () => wrapper.findComponent(GlTooltip);
afterEach(() => {
wrapper.destroy();
@ -44,25 +43,5 @@ describe('~/nav/components/top_nav_app.vue', () => {
views: TEST_NAV_DATA.views,
});
});
it('renders tooltip', () => {
expect(findTooltip().attributes()).toMatchObject({
'boundary-padding': '0',
placement: 'right',
title: TopNavApp.TOOLTIP,
});
});
});
describe('when full mounted', () => {
beforeEach(() => {
createComponent(mount);
});
it('has dropdown toggle as tooltip target', () => {
const targetFn = findTooltip().props('target');
expect(targetFn()).toBe(wrapper.find('.js-top-nav-dropdown-toggle').element);
});
});
});

View File

@ -27,20 +27,30 @@ RSpec.describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do
expect(subject.new(url: example_url, http_method: 'whatever')).not_to be_valid
end
it 'onyl allow urls as upload urls' do
it 'only allow urls as upload urls' do
expect(subject.new(url: example_url)).to be_valid
expect(subject.new(url: 'whatever')).not_to be_valid
end
end
describe '#execute' do
it 'removes the exported project file after the upload' do
allow(strategy).to receive(:send_file)
allow(strategy).to receive(:handle_response_error)
context 'when upload succeeds' do
before do
allow(strategy).to receive(:send_file)
allow(strategy).to receive(:handle_response_error)
end
expect(project).to receive(:remove_exports)
it 'does not remove the exported project file after the upload' do
expect(project).not_to receive(:remove_exports)
strategy.execute(user, project)
strategy.execute(user, project)
end
it 'has finished export status' do
strategy.execute(user, project)
expect(project.export_status).to eq(:finished)
end
end
context 'when upload fails' do

View File

@ -590,6 +590,32 @@ RSpec.describe API::Unleash do
}]
}])
end
it 'returns new flags when legacy flags are disabled' do
stub_feature_flags(remove_legacy_flags_override: false, remove_legacy_flags: true)
feature_flag_a = create(:operations_feature_flag, :new_version_flag, project: project,
name: 'feature_a', active: true)
strategy = create(:operations_strategy, feature_flag: feature_flag_a,
name: 'userWithId', parameters: { userIds: 'user8' })
create(:operations_scope, strategy: strategy, environment_scope: 'staging')
feature_flag_b = create(:operations_feature_flag, :legacy_flag, project: project,
name: 'feature_b', active: true)
create(:operations_feature_flag_scope, feature_flag: feature_flag_b,
active: true, strategies: [{ name: 'default', parameters: {} }], environment_scope: 'staging')
get api(features_url), headers: { 'UNLEASH-INSTANCEID' => client.token, 'UNLEASH-APPNAME' => 'staging' }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['features'].sort_by {|f| f['name']}).to eq([{
'name' => 'feature_a',
'enabled' => true,
'strategies' => [{
'name' => 'userWithId',
'parameters' => { 'userIds' => 'user8' }
}]
}])
end
end
end
end