Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-08-27 03:09:56 +00:00
parent dac0f02eea
commit c898498c37
25 changed files with 198 additions and 191 deletions

View File

@ -727,11 +727,9 @@ const Api = {
.replace(':id', encodeURIComponent(id))
.replace(':merge_request_iid', mergeRequestId);
const params = {};
if (gon.features.asyncMergeRequestPipelineCreation) {
params.async = true;
}
const params = {
async: true,
};
return axios.post(url, params);
},

View File

@ -9,7 +9,6 @@ import PipelinesMixin from '~/ci/pipeline_details/mixins/pipelines_mixin';
import PipelinesService from '~/ci/pipelines_page/services/pipelines_service';
import PipelineStore from '~/ci/pipeline_details/stores/pipelines_store';
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { s__, __ } from '~/locale';
export default {
@ -23,8 +22,13 @@ export default {
PipelinesTable,
TablePagination,
},
mixins: [PipelinesMixin, glFeatureFlagMixin()],
mixins: [PipelinesMixin],
props: {
canCreatePipelineInTargetProject: {
type: Boolean,
required: false,
default: false,
},
endpoint: {
type: String,
required: true,
@ -37,16 +41,21 @@ export default {
type: String,
required: true,
},
viewType: {
type: String,
required: false,
default: 'root',
},
canCreatePipelineInTargetProject: {
isMergeRequestTable: {
type: Boolean,
required: false,
default: false,
},
mergeRequestId: {
type: Number,
required: false,
default: 0,
},
projectId: {
type: String,
required: false,
default: '',
},
sourceProjectFullPath: {
type: String,
required: false,
@ -57,15 +66,10 @@ export default {
required: false,
default: '',
},
projectId: {
viewType: {
type: String,
required: false,
default: '',
},
mergeRequestId: {
type: Number,
required: false,
default: 0,
default: 'root',
},
},
@ -82,9 +86,6 @@ export default {
},
computed: {
isUsingAsyncPipelineCreation() {
return this.glFeatures?.asyncMergeRequestPipelineCreation;
},
shouldRenderTable() {
return !this.isLoading && this.state.pipelines.length > 0 && !this.hasError;
},
@ -145,7 +146,7 @@ export default {
const pipelines = resp.data.pipelines || resp.data;
this.store.storePagination(resp.headers);
this.setCommonData(pipelines, this.isUsingAsyncPipelineCreation);
this.setCommonData(pipelines, this.isMergeRequestTable);
if (resp.headers?.['x-total']) {
const updatePipelinesEvent = new CustomEvent('update-pipelines-count', {
@ -172,7 +173,7 @@ export default {
eventHub.$emit('runMergeRequestPipeline', {
projectId: this.projectId,
mergeRequestId: this.mergeRequestId,
isAsync: this.isUsingAsyncPipelineCreation,
isAsync: this.isMergeRequestTable,
});
},
tryRunPipeline() {

View File

@ -83,9 +83,6 @@ export default {
kubernetes: 'kubernetes-overview',
},
methods: {
linkClass(index) {
return index === this.currentTabIndex ? 'gl-shadow-inner-b-2-theme-accent' : '';
},
updateCurrentTab() {
const hasKubernetesIntegration = this.environment?.clusterAgent;
const selectedTabFromUrl = getParameterValues('tab');
@ -109,7 +106,6 @@ export default {
<gl-tab
:title="$options.i18n.kubernetesOverview"
:query-param-value="$options.params.kubernetes"
:title-link-class="linkClass(0)"
>
<kubernetes-overview
:environment-name="environmentName"
@ -120,7 +116,7 @@ export default {
/>
</gl-tab>
<gl-tab :query-param-value="$options.params.deployments" :title-link-class="linkClass(1)">
<gl-tab :query-param-value="$options.params.deployments">
<template #title>
{{ $options.i18n.deploymentHistory }}
<gl-badge class="gl-tab-counter-badge">{{ environment.deploymentsDisplayCount }}</gl-badge>

View File

@ -440,7 +440,7 @@ export default class LabelsSelect {
const tooltipTitleTemplate = template(
[
'<% if (isScopedLabel(label) && enableScopedLabels) { %>',
"<span class='font-weight-bold scoped-label-tooltip-title'>Scoped label</span>",
"<span class='font-weight-bold'>Scoped label</span>",
'<br>',
'<%= escapeStr(label.description) %>',
'<% } else { %>',

View File

@ -0,0 +1,54 @@
import { pick, has } from 'lodash';
/**
* @param source
* @param properties original list of searched collection
* @returns {{}} reduced source to only include properties
*/
export const pickProperties = (source, properties = []) => {
if (!source) {
return {};
}
/**
* If no properties provided
* search would be executed on provided properties
*/
if (!properties || properties.length === 0) {
return source;
}
properties.forEach((property) => {
if (!has(source, property)) {
throw new Error(`${property} does not exist on object. Please provide valid property list.`);
}
});
return pick(source, properties);
};
/**
* Search among provided properties on items
* @param items original list of searched collection
* @param properties list of properties to search in
* @param searchQuery search query
* @returns {*[]}
*/
export const searchInItemsProperties = ({ items = [], properties = [], searchQuery = '' } = {}) => {
if (!items || items.length === 0) {
return [];
}
if (searchQuery === '') {
return items;
}
const containsValue = (value) =>
value.toString().toLowerCase().includes(searchQuery.toLowerCase());
return items.filter((item) => {
const reducedSource = pickProperties(item, properties);
return Object.values(reducedSource).some((value) => containsValue(value));
});
};

View File

@ -119,6 +119,7 @@ function mountPipelines() {
targetProjectFullPath: mrWidgetData?.target_project_full_path || '',
projectId: pipelineTableViewEl.dataset.projectId,
mergeRequestId: mrWidgetData ? mrWidgetData.iid : null,
isMergeRequestTable: true,
},
});
},

View File

@ -16,7 +16,7 @@ Default.args = {
status: {
icon: 'status_success',
text: 'Success',
detailsPath: 'https://gitab.com/',
detailsPath: 'https://gitlab.com/',
},
};
@ -25,7 +25,7 @@ WithText.args = {
status: {
icon: 'status_success',
text: 'Success',
detailsPath: 'https://gitab.com/',
detailsPath: 'https://gitlab.com/',
},
showStatusText: true,
};

View File

@ -1,6 +1,7 @@
<script>
import { GlFormGroup, GlCollapsibleListbox } from '@gitlab/ui';
import { __ } from '~/locale';
import { searchInItemsProperties } from '~/lib/utils/search_utils';
const MIN_ITEMS_COUNT_FOR_SEARCHING = 10;
@ -112,7 +113,11 @@ export default {
.map(({ text, options }) => {
return {
text,
options: options.filter((option) => option.text.toLowerCase().includes(searchString)),
options: searchInItemsProperties({
items: options,
properties: ['text'],
searchQuery: searchString,
}),
};
})
.filter(({ options }) => options.length);

View File

@ -495,11 +495,6 @@ li.note {
padding-right: $gl-spacing-scale-3;
}
// used in the Markdown rendering of labels
.scoped-label-tooltip-title {
color: var(--theme-indigo-300, $theme-indigo-300);
}
.gl-label-scoped {
box-shadow: 0 0 0 2px currentColor inset;
}

View File

@ -436,7 +436,7 @@ $gl-bronze-plan: #cd7f32;
Performance Bar
*/
$perf-bar-production: $gray-950;
$perf-bar-staging: $theme-indigo-950;
$perf-bar-staging: $purple-950;
$perf-bar-development: $red-900;
$perf-bar-bucket-bg: $black;
$perf-bar-bucket-box-shadow-from: rgba($white, 0.2);

View File

@ -9,35 +9,35 @@
margin-bottom: $gl-padding-8;
&.ui-indigo {
background-color: $theme-indigo-900;
background-color: var(--gl-color-theme-indigo-900);
}
&.ui-light-indigo {
background-color: $theme-indigo-700;
background-color: var(--gl-color-theme-indigo-700);
}
&.ui-blue {
background-color: $theme-blue-900;
background-color: var(--gl-color-theme-blue-900);
}
&.ui-light-blue {
background-color: $theme-light-blue-800;
background-color: var(--gl-color-theme-light-blue-800);
}
&.ui-green {
background-color: $theme-green-900;
background-color: var(--gl-color-theme-green-900);
}
&.ui-light-green {
background-color: $theme-green-800;
background-color: var(--gl-color-theme-green-800);
}
&.ui-red {
background-color: $theme-red-900;
background-color: var(--gl-color-theme-red-900);
}
&.ui-light-red {
background-color: $theme-light-red-700;
background-color: var(--gl-color-theme-light-red-700);
}
&.ui-gray {

View File

@ -45,7 +45,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:ci_graphql_pipeline_mini_graph, project)
push_frontend_feature_flag(:notifications_todos_buttons, current_user)
push_frontend_feature_flag(:reviewer_assign_drawer, current_user)
push_frontend_feature_flag(:async_merge_request_pipeline_creation, current_user)
end
around_action :allow_gitaly_ref_name_caching, only: [:index, :show, :diffs, :rapid_diffs, :discussions]

View File

@ -98,7 +98,7 @@ module Repositories
return unless project
return if Gitlab::Database.read_only?
return unless repo_type.project?
return if skip_fetch_statistics_increment?
return if Feature.enabled?(:disable_git_http_fetch_writes)
Projects::FetchStatisticsIncrementService.new(project).execute
end
@ -146,14 +146,6 @@ module Repositories
payload[:metadata] ||= {}
payload[:metadata][:repository_storage] = project&.repository_storage
end
def skip_fetch_statistics_increment?
# Since disable_git_http_fetch_writes FF does not define a feature flag actor,
# it is currently not possible to increment the project statistics without enabling
# or disabling it for all projects. The allow_git_http_fetch_writes FF allow us to control this.
Feature.enabled?(:disable_git_http_fetch_writes) &&
Feature.disabled?(:allow_git_http_fetch_writes, project, type: :beta)
end
end
end

View File

@ -1,9 +0,0 @@
---
name: allow_git_http_fetch_writes
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/426270
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/159835
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/473133
milestone: '17.3'
group: group::source code
type: beta
default_enabled: false

View File

@ -1,9 +0,0 @@
---
name: async_merge_request_pipeline_creation
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/463355
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/161407
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/476625
milestone: '17.3'
group: group::pipeline authoring
type: wip
default_enabled: false

View File

@ -221,7 +221,6 @@ class ConvertTableToListPartitioning < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
TABLE_NAME = :table_name
TABLE_FK = :table_references_by_fk
PARENT_TABLE_NAME = :p_table_name
FIRST_PARTITION = 100
PARTITION_COLUMN = :partition_id
@ -231,8 +230,7 @@ class ConvertTableToListPartitioning < Gitlab::Database::Migration[2.1]
table_name: TABLE_NAME,
partitioning_column: PARTITION_COLUMN,
parent_table_name: PARENT_TABLE_NAME,
initial_partitioning_value: FIRST_PARTITION,
lock_tables: [TABLE_FK, TABLE_NAME]
initial_partitioning_value: FIRST_PARTITION
)
end

View File

@ -205,8 +205,8 @@ To clean up a repository:
`filter-repo` directory.
If your `commit-map` file is too large, the background cleanup process might time out and fail.
As a result, the repository size isn't reduced as expected.
To address this, split the file and upload it in parts, for example:
As a result, the repository size isn't reduced as expected. To address this, split the file and
upload it in parts. Start with `20000` and reduce as needed. For example:
```shell
split -l 20000 filter-repo/commit-map filter-repo/commit-map-

View File

@ -89,7 +89,7 @@ To insert a table:
1. Select **Insert table** **{table}**.
1. From the dropdown list, select the dimensions of the new table.
![Alt text](img/rich_text_editor_02_v16_2.png)
![A table size selector with 3 rows and 3 columns.](img/rich_text_editor_02_v16_2.png)
### Edit a table
@ -97,7 +97,7 @@ Inside a table cell, you can use a menu to insert or delete rows or columns.
To open the menu: In the upper-right corner of a cell, select the chevron **{chevron-down}**.
![Alt text](img/rich_text_editor_03_v16_2.png)
![An active chevron menu showing table actions.](img/rich_text_editor_03_v16_2.png)
### Operations on multiple cells

View File

@ -26,14 +26,6 @@ RSpec.describe Repositories::GitHttpController, feature_category: :source_code_m
end
end
shared_examples 'increments fetch statistics' do
it 'calls Projects::FetchStatisticsIncrementService service' do
expect(Projects::FetchStatisticsIncrementService).to receive(:new).with(project).and_call_original
send_request
end
end
shared_examples 'handles user activity' do
it 'updates the user activity' do
activity_project = container.is_a?(PersonalSnippet) ? nil : project
@ -149,24 +141,10 @@ RSpec.describe Repositories::GitHttpController, feature_category: :source_code_m
stub_feature_flags(disable_git_http_fetch_writes: true)
end
context 'and allow_git_http_fetch_writes is disabled' do
before do
stub_feature_flags(allow_git_http_fetch_writes: false)
end
it 'does not increment statistics' do
expect(Projects::FetchStatisticsIncrementService).not_to receive(:new)
it 'does not increment statistics' do
expect(Projects::FetchStatisticsIncrementService).not_to receive(:new)
send_request
end
end
context 'and allow_git_http_fetch_writes is enabled' do
before do
stub_feature_flags(allow_git_http_fetch_writes: true)
end
it_behaves_like 'increments fetch statistics'
send_request
end
end
@ -175,20 +153,10 @@ RSpec.describe Repositories::GitHttpController, feature_category: :source_code_m
stub_feature_flags(disable_git_http_fetch_writes: false)
end
context 'and allow_git_http_fetch_writes is disabled' do
before do
stub_feature_flags(allow_git_http_fetch_writes: false)
end
it 'increments statistics' do
expect(Projects::FetchStatisticsIncrementService).to receive(:new).with(project).and_call_original
it_behaves_like 'increments fetch statistics'
end
context 'and allow_git_http_fetch_writes is enabled' do
before do
stub_feature_flags(allow_git_http_fetch_writes: true)
end
it_behaves_like 'increments fetch statistics'
send_request
end
end
end

View File

@ -955,11 +955,9 @@ describe('Api', () => {
const dummyProjectId = 5;
const dummyMergeRequestIid = 123;
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/5/merge_requests/123/pipelines`;
const features = { asyncMergeRequestPipelineCreation: true };
beforeEach(() => {
mock = new MockAdapter(axios);
window.gon.features = features;
});
it('creates a merge request pipeline async', () => {
@ -976,25 +974,6 @@ describe('Api', () => {
expect(axios.post).toHaveBeenCalledWith(expectedUrl, { async: true });
});
});
describe('when asyncMergeRequestPipelineCreation is disabled', () => {
it('creates a merge request pipeline synchronously', () => {
window.gon.features.asyncMergeRequestPipelineCreation = false;
jest.spyOn(axios, 'post');
mock.onPost(expectedUrl).replyOnce(HTTP_STATUS_OK, {
id: 456,
});
return Api.postMergeRequestPipeline(dummyProjectId, {
mergeRequestId: dummyMergeRequestIid,
}).then(({ data }) => {
expect(data.id).toBe(456);
expect(axios.post).toHaveBeenCalledWith(expectedUrl, {});
});
});
});
});
describe('projectForks', () => {

View File

@ -5,8 +5,6 @@ describe('Pipelines Store', () => {
beforeEach(() => {
store = new PipelineStore();
window.gon.features = { asyncMergeRequestPipelineCreation: false };
});
it('should be initialized with an empty state', () => {
@ -32,11 +30,7 @@ describe('Pipelines Store', () => {
expect(store.state.pipelines).toEqual(array);
});
describe('when asyncMergeRequestPipelineCreation is enabled', () => {
beforeEach(() => {
window.gon.features.asyncMergeRequestPipelineCreation = true;
});
describe('when pipeline creation is async', () => {
describe('when a new pipeline is added to the store', () => {
it('sets the value of `isRunningMergeRequestPipeline` to false', () => {
const existingPipelines = [{ created_at: '2023' }];
@ -53,11 +47,11 @@ describe('Pipelines Store', () => {
describe('when no new pipelines are added to the store', () => {
it('does not change the value of `isRunningMergeRequestPipeline`', () => {
const existingPipelines = [{ created_at: '2023' }];
store.storePipelines(existingPipelines);
store.storePipelines(existingPipelines, true);
store.state.isRunningMergeRequestPipeline = true;
const updatedPipelines = [{ created_at: '2023' }];
store.storePipelines(updatedPipelines);
store.storePipelines(updatedPipelines, true);
expect(store.state.isRunningMergeRequestPipeline).toBe(true);
});

View File

@ -42,11 +42,7 @@ describe('Pipelines table in Commits and Merge requests', () => {
const findUserPermissionsDocsLink = () => wrapper.findByTestId('user-permissions-docs-link');
const findPipelinesTable = () => wrapper.findComponent(PipelinesTable);
const createComponent = ({
props = {},
mountFn = mountExtended,
asyncMergeRequestPipelineCreation = true,
} = {}) => {
const createComponent = ({ props = {}, mountFn = mountExtended } = {}) => {
wrapper = mountFn(LegacyPipelinesTableWrapper, {
propsData: {
endpoint: 'endpoint.json',
@ -54,11 +50,6 @@ describe('Pipelines table in Commits and Merge requests', () => {
errorStateSvgPath: 'foo',
...props,
},
provide: {
glFeatures: {
asyncMergeRequestPipelineCreation,
},
},
mocks: {
$toast,
},
@ -236,7 +227,20 @@ describe('Pipelines table in Commits and Merge requests', () => {
jest.spyOn(Api, 'postMergeRequestPipeline').mockResolvedValue();
});
describe('when asyncMergeRequestPipelineCreation is enabled', () => {
describe('when the table is a merge request table', () => {
beforeEach(async () => {
createComponent({
props: {
canRunPipeline: true,
isMergeRequestTable: true,
mergeRequestId: 3,
projectId: '5',
},
});
await waitForPromises();
});
it('on desktop, shows a loading button', async () => {
await findRunPipelineBtn().trigger('click');
@ -262,20 +266,7 @@ describe('Pipelines table in Commits and Merge requests', () => {
});
});
describe('when asyncMergeRequestPipelineCreation is disabled', () => {
beforeEach(async () => {
createComponent({
props: {
canRunPipeline: true,
projectId: '5',
mergeRequestId: 3,
},
asyncMergeRequestPipelineCreation: false,
});
await waitForPromises();
});
describe('when the table is not a merge request table', () => {
it('displays a toast message during pipeline creation', async () => {
await findRunPipelineBtn().trigger('click');

View File

@ -1,6 +1,6 @@
import { GlLoadingIcon, GlTabs, GlTab, GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { updateHistory, getParameterValues, setUrlParams } from '~/lib/utils/url_utility';
import EnvironmentsDetailPage from '~/environments/environment_details/index.vue';
@ -91,19 +91,6 @@ describe('~/environments/environment_details/index.vue', () => {
it('renders tabs component with the correct prop', () => {
expect(findTabs().props('syncActiveTabWithQueryParams')).toBe(true);
});
it('sets proper CSS class to the active tab', () => {
expect(findTabByIndex(0).props('titleLinkClass')).toBe('gl-shadow-inner-b-2-theme-accent');
expect(findTabByIndex(1).props('titleLinkClass')).toBe('');
});
it('updates the CSS class when the active tab changes', async () => {
findTabs().vm.$emit('input', 1);
await nextTick();
expect(findTabByIndex(0).props('titleLinkClass')).toBe('');
expect(findTabByIndex(1).props('titleLinkClass')).toBe('gl-shadow-inner-b-2-theme-accent');
});
});
describe('kubernetes overview tab', () => {

View File

@ -103,7 +103,7 @@ describe('LabelsSelect', () => {
it('generated label item template has correct title for tooltip', () => {
expect($labelEl.find('a').attr('title')).toBe(
"<span class='font-weight-bold scoped-label-tooltip-title'>Scoped label</span><br>Foobar",
"<span class='font-weight-bold'>Scoped label</span><br>Foobar",
);
});

View File

@ -0,0 +1,67 @@
import { pickProperties, searchInItemsProperties } from '~/lib/utils/search_utils';
const items = [1, 2].map((id) => ({
id,
name: `name ${id}`,
text: `text ${id}`,
title: `title ${id}`,
}));
describe('pickProperties', () => {
it.each`
source | properties | outcome
${undefined} | ${[]} | ${{}}
${null} | ${[]} | ${{}}
${{}} | ${[]} | ${{}}
${items[0]} | ${[]} | ${items[0]}
${items[0]} | ${undefined} | ${items[0]}
${items[0]} | ${null} | ${items[0]}
${items[0]} | ${['name']} | ${{ name: 'name 1' }}
${items[0]} | ${['name', 'text', 'id']} | ${{ name: 'name 1', text: 'text 1', id: 1 }}
`('picks specific properties from source object', ({ source, properties, outcome }) => {
expect(pickProperties(source, properties)).toEqual(outcome);
});
it('throws an error if property does not exist on source object', () => {
try {
pickProperties(items[0], ['name', 'text1']);
} catch (error) {
expect(error).toEqual(
new Error('text1 does not exist on object. Please provide valid property list.'),
);
}
});
});
describe('searchInItemsProperties', () => {
it.each`
items | properties | searchQuery | outcome
${undefined} | ${[]} | ${''} | ${[]}
${null} | ${[]} | ${''} | ${[]}
${[]} | ${[]} | ${''} | ${[]}
${items} | ${[]} | ${''} | ${items}
${items} | ${[]} | ${'name 1'} | ${[items[0]]}
${items} | ${['text']} | ${'name 1'} | ${[]}
${items} | ${['text']} | ${'text 1'} | ${[items[0]]}
${items} | ${['text', 'name']} | ${'text 2'} | ${[items[1]]}
${items} | ${['text', 'name', 'title']} | ${'text 2'} | ${[items[1]]}
${items} | ${['name', 'title']} | ${'text 2'} | ${[]}
`(
'filters items based on search query and picked properties',
({ items: mockedItems, properties, searchQuery, outcome }) => {
expect(searchInItemsProperties({ items: mockedItems, properties, searchQuery })).toEqual(
outcome,
);
},
);
it('throws an error if property does not exist on source objects when searched', () => {
try {
searchInItemsProperties({ items, properties: ['name', 'text1'], searchQuery: 'text 1' });
} catch (error) {
expect(error).toEqual(
new Error('text1 does not exist on object. Please provide valid property list.'),
);
}
});
});