Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-08-30 06:09:35 +00:00
parent 2135111f19
commit abbedc2027
16 changed files with 311 additions and 58 deletions

View file

@ -1,3 +1,5 @@
export const MINIMUM_SEARCH_LENGTH = 3;
export const TYPE_CI_RUNNER = 'Ci::Runner';
export const TYPE_EPIC = 'Epic';
export const TYPE_GROUP = 'Group';

View file

@ -0,0 +1,98 @@
<script>
import {
GlButton,
GlButtonGroup,
GlDropdown,
GlDropdownItem,
GlDropdownSectionHeader,
GlLoadingIcon,
GlSearchBoxByType,
} from '@gitlab/ui';
import { MINIMUM_SEARCH_LENGTH } from '~/graphql_shared/constants';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import Tracking from '~/tracking';
import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants';
import searchNamespacesWhereUserCanCreateProjectsQuery from '../queries/search_namespaces_where_user_can_create_projects.query.graphql';
export default {
components: {
GlButton,
GlButtonGroup,
GlDropdown,
GlDropdownItem,
GlDropdownSectionHeader,
GlLoadingIcon,
GlSearchBoxByType,
},
mixins: [Tracking.mixin()],
apollo: {
currentUser: {
query: searchNamespacesWhereUserCanCreateProjectsQuery,
variables() {
return {
search: this.search,
};
},
skip() {
return this.search.length > 0 && this.search.length < MINIMUM_SEARCH_LENGTH;
},
debounce: DEBOUNCE_DELAY,
},
},
inject: ['namespaceFullPath', 'namespaceId', 'rootUrl', 'trackLabel'],
data() {
return {
currentUser: {},
search: '',
selectedNamespace: {
id: this.namespaceId,
fullPath: this.namespaceFullPath,
},
};
},
computed: {
userGroups() {
return this.currentUser.groups?.nodes || [];
},
userNamespace() {
return this.currentUser.namespace || {};
},
},
methods: {
handleClick({ id, fullPath }) {
this.selectedNamespace = {
id: getIdFromGraphQLId(id),
fullPath,
};
},
},
};
</script>
<template>
<gl-button-group class="gl-w-full">
<gl-button label>{{ rootUrl }}</gl-button>
<gl-dropdown
class="gl-w-full"
:text="selectedNamespace.fullPath"
toggle-class="gl-rounded-top-right-base! gl-rounded-bottom-right-base!"
data-qa-selector="select_namespace_dropdown"
@show="track('activate_form_input', { label: trackLabel, property: 'project_path' })"
>
<gl-search-box-by-type v-model.trim="search" />
<gl-loading-icon v-if="$apollo.queries.currentUser.loading" />
<template v-else>
<gl-dropdown-section-header>{{ __('Groups') }}</gl-dropdown-section-header>
<gl-dropdown-item v-for="group of userGroups" :key="group.id" @click="handleClick(group)">
{{ group.fullPath }}
</gl-dropdown-item>
<gl-dropdown-section-header>{{ __('Users') }}</gl-dropdown-section-header>
<gl-dropdown-item @click="handleClick(userNamespace)">
{{ userNamespace.fullPath }}
</gl-dropdown-item>
</template>
</gl-dropdown>
<input type="hidden" name="project[namespace_id]" :value="selectedNamespace.id" />
</gl-button-group>
</template>

View file

@ -1,13 +1,15 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import initProjectVisibilitySelector from '../../../project_visibility';
import initProjectNew from '../../../projects/project_new';
import NewProjectCreationApp from './components/app.vue';
import NewProjectUrlSelect from './components/new_project_url_select.vue';
initProjectVisibilitySelector();
initProjectNew.bindEvents();
function initNewProjectCreation() {
const el = document.querySelector('.js-new-project-creation');
function initNewProjectCreation(el) {
const {
pushToCreateProjectCommand,
workingWithProjectsHelpPath,
@ -29,9 +31,6 @@ function initNewProjectCreation(el) {
return new Vue({
el,
components: {
NewProjectCreationApp,
},
provide,
render(h) {
return h(NewProjectCreationApp, { props });
@ -39,6 +38,31 @@ function initNewProjectCreation(el) {
});
}
const el = document.querySelector('.js-new-project-creation');
function initNewProjectUrlSelect() {
const el = document.querySelector('.js-vue-new-project-url-select');
initNewProjectCreation(el);
if (!el) {
return undefined;
}
Vue.use(VueApollo);
return new Vue({
el,
apolloProvider: new VueApollo({
defaultClient: createDefaultClient({}, { assumeImmutableResults: true }),
}),
provide: {
namespaceFullPath: el.dataset.namespaceFullPath,
namespaceId: el.dataset.namespaceId,
rootUrl: el.dataset.rootUrl,
trackLabel: el.dataset.trackLabel,
},
render: (createElement) => createElement(NewProjectUrlSelect),
});
}
initProjectVisibilitySelector();
initProjectNew.bindEvents();
initNewProjectCreation();
initNewProjectUrlSelect();

View file

@ -0,0 +1,14 @@
query searchNamespacesWhereUserCanCreateProjects($search: String) {
currentUser {
groups(permissionScope: CREATE_PROJECTS, search: $search) {
nodes {
id
fullPath
}
}
namespace {
id
fullPath
}
}
}

View file

@ -136,6 +136,9 @@ export default {
refUrl() {
return this.commitRef.ref_url || this.commitRef.path;
},
tooltipTitle() {
return this.mergeRequestRef ? this.mergeRequestRef.title : this.commitRef.name;
},
},
};
</script>
@ -148,23 +151,14 @@ export default {
<gl-icon v-else name="branch" />
</div>
<gl-link
v-if="mergeRequestRef"
v-gl-tooltip
:href="mergeRequestRef.path"
:title="mergeRequestRef.title"
class="ref-name"
>{{ mergeRequestRef.iid }}</gl-link
>
<gl-link
v-else
v-gl-tooltip
:href="refUrl"
:title="commitRef.name"
class="ref-name"
data-testid="ref-name"
>{{ commitRef.name }}</gl-link
>
<tooltip-on-truncate :title="tooltipTitle" truncate-target="child" placement="top">
<gl-link v-if="mergeRequestRef" :href="mergeRequestRef.path" class="ref-name">
{{ mergeRequestRef.iid }}
</gl-link>
<gl-link v-else :href="refUrl" class="ref-name" data-testid="ref-name">
{{ commitRef.name }}
</gl-link>
</tooltip-on-truncate>
</template>
<gl-icon name="commit" class="commit-icon js-commit-icon" />

View file

@ -4,9 +4,8 @@
%fieldset
%h5= _('Permissions')
- unless ::Feature.enabled?(:saas_user_caps)
.form-group
= render 'shared/allow_request_access', form: f
.form-group
= render 'shared/allow_request_access', form: f
- if @group.root?
.form-group.gl-mb-3
@ -44,6 +43,5 @@
= render_if_exists 'groups/settings/prevent_forking', f: f, group: @group
= render 'groups/settings/two_factor_auth', f: f, group: @group
= render_if_exists 'groups/personal_access_token_expiration_policy', f: f, group: @group
= render_if_exists 'groups/settings/membership', f: f
= render_if_exists 'groups/member_lock_setting', f: f, group: @group
= f.submit _('Save changes'), class: 'btn gl-button btn-confirm gl-mt-3 js-dirty-submit', data: { qa_selector: 'save_permissions_changes_button' }

View file

@ -14,17 +14,19 @@
%span= s_("Project URL")
.input-group.gl-flex-nowrap
- if current_user.can_select_namespace?
.input-group-prepend.flex-shrink-0.has-tooltip{ title: root_url }
.input-group-text
= root_url
- namespace_id = namespace_id_from(params)
= f.select(:namespace_id,
namespaces_options_with_developer_maintainer_access(selected: namespace_id,
display_path: true,
extra_group: namespace_id),
{},
{ class: 'select2 js-select-namespace qa-project-namespace-select block-truncated', data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "project_path", track_value: "", qa_selector: "select_namespace_dropdown" }})
- if Feature.enabled?(:paginatable_namespace_drop_down_for_project_creation, current_user, default_enabled: :yaml)
.js-vue-new-project-url-select{ data: { namespace_full_path: GroupFinder.new(current_user).execute(id: namespace_id)&.full_path, namespace_id: namespace_id, root_url: root_url, track_label: track_label } }
- else
.input-group-prepend.flex-shrink-0.has-tooltip{ title: root_url }
.input-group-text
= root_url
= f.select(:namespace_id,
namespaces_options_with_developer_maintainer_access(selected: namespace_id,
display_path: true,
extra_group: namespace_id),
{},
{ class: 'select2 js-select-namespace qa-project-namespace-select block-truncated', data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "project_path", track_value: "", qa_selector: "select_namespace_dropdown" }})
- else
.input-group-prepend.static-namespace.flex-shrink-0.has-tooltip{ title: user_url(current_user.username) + '/' }
.input-group-text.border-0

View file

@ -1,8 +0,0 @@
---
name: saas_user_caps
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66264
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/336251
milestone: '14.1'
type: development
group: group::utilization
default_enabled: false

View file

@ -127,3 +127,11 @@ some sort of log aggregation software like Loki, ELK, Splunk, or others.
You can use a tool like Ansible or PSSH (parallel SSH) that can execute identical commands across your servers in
parallel, or craft your own solution.
### Viewing the request in the Performance Bar
You can use the [performance bar](../monitoring/performance/performance_bar.md) to view interesting data including calls made to SQL and Gitaly.
To view the data, the correlation ID of the request must match the same session as the user
viewing the performance bar. For API requests, this means that you must perform the request
using the session cookie of the signed-in user.

View file

@ -46,7 +46,7 @@ The security dashboard and vulnerability report displays information about vulne
## Pipeline Security
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13496) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.3.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13496) in GitLab 12.3.
At the pipeline level, the Security section displays the vulnerabilities present in the branch of
the project the pipeline ran against.
@ -64,7 +64,7 @@ the analyzer outputs an
### Scan details
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3728) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.10.
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3728) in GitLab 13.10.
The **Scan details** section lists the scans run in the pipeline and the total number of
vulnerabilities per scan. For the DAST scan, select **Download scanned resources** to download a
@ -104,7 +104,7 @@ To download an SVG image of the chart, select **Save chart to an image** (**{dow
## Group Security Dashboard
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/6709) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.5.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/6709) in GitLab 11.5.
The group Security Dashboard gives an overview of the vulnerabilities found in the default branches of the
projects in a group and its subgroups. Access it by navigating to **Security > Security Dashboard**
@ -139,7 +139,7 @@ Navigate to the group's [vulnerability report](../vulnerability_report/index.md)
## Security Center
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3426) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.4.
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3426) in GitLab 13.4.
The Security Center is personal space where you manage vulnerabilities across all your projects. It
displays the vulnerabilities present in the default branches of all the projects you configure. It includes

View file

@ -20820,9 +20820,6 @@ msgstr ""
msgid "Members of a group may only view projects they have permission to access"
msgstr ""
msgid "Membership"
msgstr ""
msgid "Members|%{time} by %{user}"
msgstr ""

View file

@ -10,6 +10,7 @@ RSpec.describe 'Import/Export - project import integration test', :js do
let(:export_path) { "#{Dir.tmpdir}/import_file_spec" }
before do
stub_feature_flags(paginatable_namespace_drop_down_for_project_creation: false)
stub_uploads_object_storage(FileUploader)
allow_next_instance_of(Gitlab::ImportExport) do |instance|
allow(instance).to receive(:storage_path).and_return(export_path)

View file

@ -6,6 +6,10 @@ RSpec.describe 'New project', :js do
include Select2Helper
include Spec::Support::Helpers::Features::TopNavSpecHelpers
before do
stub_feature_flags(paginatable_namespace_drop_down_for_project_creation: false)
end
context 'as a user' do
let(:user) { create(:user) }

View file

@ -6,6 +6,7 @@ RSpec.describe 'User creates a project', :js do
let(:user) { create(:user) }
before do
stub_feature_flags(paginatable_namespace_drop_down_for_project_creation: false)
sign_in(user)
create(:personal_key, user: user)
end

View file

@ -0,0 +1,122 @@
import { GlButton, GlDropdown, GlDropdownItem, GlDropdownSectionHeader } from '@gitlab/ui';
import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import NewProjectUrlSelect from '~/pages/projects/new/components/new_project_url_select.vue';
import searchQuery from '~/pages/projects/new/queries/search_namespaces_where_user_can_create_projects.query.graphql';
describe('NewProjectUrlSelect component', () => {
let wrapper;
const data = {
currentUser: {
groups: {
nodes: [
{
id: 'gid://gitlab/Group/26',
fullPath: 'flightjs',
},
{
id: 'gid://gitlab/Group/28',
fullPath: 'h5bp',
},
],
},
namespace: {
id: 'gid://gitlab/Namespace/1',
fullPath: 'root',
},
},
};
const localVue = createLocalVue();
localVue.use(VueApollo);
const requestHandlers = [[searchQuery, jest.fn().mockResolvedValue({ data })]];
const apolloProvider = createMockApollo(requestHandlers);
const provide = {
namespaceFullPath: 'h5bp',
namespaceId: '28',
rootUrl: 'https://gitlab.com/',
trackLabel: 'blank_project',
};
const mountComponent = ({ mountFn = shallowMount } = {}) =>
mountFn(NewProjectUrlSelect, { localVue, apolloProvider, provide });
const findButtonLabel = () => wrapper.findComponent(GlButton);
const findDropdown = () => wrapper.findComponent(GlDropdown);
const findHiddenInput = () => wrapper.find('input');
afterEach(() => {
wrapper.destroy();
});
it('renders the root url as a label', () => {
wrapper = mountComponent();
expect(findButtonLabel().text()).toBe(provide.rootUrl);
expect(findButtonLabel().props('label')).toBe(true);
});
it('renders a dropdown with the initial namespace full path as the text', () => {
wrapper = mountComponent();
expect(findDropdown().props('text')).toBe(provide.namespaceFullPath);
});
it('renders a dropdown with the initial namespace id in the hidden input', () => {
wrapper = mountComponent();
expect(findHiddenInput().attributes('value')).toBe(provide.namespaceId);
});
it('renders expected dropdown items', async () => {
wrapper = mountComponent({ mountFn: mount });
jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick();
const listItems = wrapper.findAll('li');
expect(listItems.at(0).findComponent(GlDropdownSectionHeader).text()).toBe('Groups');
expect(listItems.at(1).text()).toBe(data.currentUser.groups.nodes[0].fullPath);
expect(listItems.at(2).text()).toBe(data.currentUser.groups.nodes[1].fullPath);
expect(listItems.at(3).findComponent(GlDropdownSectionHeader).text()).toBe('Users');
expect(listItems.at(4).text()).toBe(data.currentUser.namespace.fullPath);
});
it('updates hidden input with selected namespace', async () => {
wrapper = mountComponent();
jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick();
wrapper.findComponent(GlDropdownItem).vm.$emit('click');
await wrapper.vm.$nextTick();
expect(findHiddenInput().attributes()).toMatchObject({
name: 'project[namespace_id]',
value: getIdFromGraphQLId(data.currentUser.groups.nodes[0].id).toString(),
});
});
it('tracks clicking on the dropdown', () => {
wrapper = mountComponent();
const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
findDropdown().vm.$emit('show');
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'activate_form_input', {
label: provide.trackLabel,
property: 'project_path',
});
unmockTracking();
});
});

View file

@ -162,8 +162,6 @@ describe('Commit component', () => {
expect(refEl.attributes('href')).toBe(props.commitRef.ref_url);
expect(refEl.attributes('title')).toBe(props.commitRef.name);
expect(findIcon('branch').exists()).toBe(true);
});
});
@ -195,8 +193,6 @@ describe('Commit component', () => {
expect(refEl.attributes('href')).toBe(props.mergeRequestRef.path);
expect(refEl.attributes('title')).toBe(props.mergeRequestRef.title);
expect(findIcon('git-merge').exists()).toBe(true);
});
});