Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
2135111f19
commit
abbedc2027
16 changed files with 311 additions and 58 deletions
|
@ -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';
|
||||
|
|
|
@ -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>
|
|
@ -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();
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
query searchNamespacesWhereUserCanCreateProjects($search: String) {
|
||||
currentUser {
|
||||
groups(permissionScope: CREATE_PROJECTS, search: $search) {
|
||||
nodes {
|
||||
id
|
||||
fullPath
|
||||
}
|
||||
}
|
||||
namespace {
|
||||
id
|
||||
fullPath
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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" />
|
||||
|
||||
|
|
|
@ -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' }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue