Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
9d485c177e
commit
d5f14b5e2c
27 changed files with 349 additions and 238 deletions
|
@ -1 +1 @@
|
|||
6941c499e077fe2303dd5c31a08807d14ad7a616
|
||||
529ef59e73a21d1abc54833c4edbe92cbcc9fb64
|
||||
|
|
|
@ -40,11 +40,6 @@ import issueSetMilestoneMutation from '../graphql/issue_set_milestone.mutation.g
|
|||
import listsIssuesQuery from '../graphql/lists_issues.query.graphql';
|
||||
import * as types from './mutation_types';
|
||||
|
||||
const notImplemented = () => {
|
||||
/* eslint-disable-next-line @gitlab/require-i18n-strings */
|
||||
throw new Error('Not implemented!');
|
||||
};
|
||||
|
||||
export const gqlClient = createGqClient(
|
||||
{},
|
||||
{
|
||||
|
@ -737,28 +732,4 @@ export default {
|
|||
unsetError: ({ commit }) => {
|
||||
commit(types.SET_ERROR, undefined);
|
||||
},
|
||||
|
||||
fetchBacklog: () => {
|
||||
notImplemented();
|
||||
},
|
||||
|
||||
bulkUpdateIssues: () => {
|
||||
notImplemented();
|
||||
},
|
||||
|
||||
fetchIssue: () => {
|
||||
notImplemented();
|
||||
},
|
||||
|
||||
toggleIssueSubscription: () => {
|
||||
notImplemented();
|
||||
},
|
||||
|
||||
showPage: () => {
|
||||
notImplemented();
|
||||
},
|
||||
|
||||
toggleEmptyState: () => {
|
||||
notImplemented();
|
||||
},
|
||||
};
|
||||
|
|
|
@ -9,9 +9,7 @@ export const GENERATE_DEFAULT_LISTS_FAILURE = 'GENERATE_DEFAULT_LISTS_FAILURE';
|
|||
export const RECEIVE_BOARD_LISTS_SUCCESS = 'RECEIVE_BOARD_LISTS_SUCCESS';
|
||||
export const RECEIVE_BOARD_LISTS_FAILURE = 'RECEIVE_BOARD_LISTS_FAILURE';
|
||||
export const SHOW_PROMOTION_LIST = 'SHOW_PROMOTION_LIST';
|
||||
export const REQUEST_ADD_LIST = 'REQUEST_ADD_LIST';
|
||||
export const RECEIVE_ADD_LIST_SUCCESS = 'RECEIVE_ADD_LIST_SUCCESS';
|
||||
export const RECEIVE_ADD_LIST_ERROR = 'RECEIVE_ADD_LIST_ERROR';
|
||||
export const MOVE_LIST = 'MOVE_LIST';
|
||||
export const UPDATE_LIST_FAILURE = 'UPDATE_LIST_FAILURE';
|
||||
export const TOGGLE_LIST_COLLAPSED = 'TOGGLE_LIST_COLLAPSED';
|
||||
|
@ -20,19 +18,11 @@ export const REMOVE_LIST_FAILURE = 'REMOVE_LIST_FAILURE';
|
|||
export const REQUEST_ITEMS_FOR_LIST = 'REQUEST_ITEMS_FOR_LIST';
|
||||
export const RECEIVE_ITEMS_FOR_LIST_FAILURE = 'RECEIVE_ITEMS_FOR_LIST_FAILURE';
|
||||
export const RECEIVE_ITEMS_FOR_LIST_SUCCESS = 'RECEIVE_ITEMS_FOR_LIST_SUCCESS';
|
||||
export const REQUEST_ADD_ISSUE = 'REQUEST_ADD_ISSUE';
|
||||
export const RECEIVE_ADD_ISSUE_SUCCESS = 'RECEIVE_ADD_ISSUE_SUCCESS';
|
||||
export const RECEIVE_ADD_ISSUE_ERROR = 'RECEIVE_ADD_ISSUE_ERROR';
|
||||
export const UPDATE_BOARD_ITEM = 'UPDATE_BOARD_ITEM';
|
||||
export const REMOVE_BOARD_ITEM = 'REMOVE_BOARD_ITEM';
|
||||
export const REQUEST_UPDATE_ISSUE = 'REQUEST_UPDATE_ISSUE';
|
||||
export const MUTATE_ISSUE_SUCCESS = 'MUTATE_ISSUE_SUCCESS';
|
||||
export const RECEIVE_UPDATE_ISSUE_SUCCESS = 'RECEIVE_UPDATE_ISSUE_SUCCESS';
|
||||
export const RECEIVE_UPDATE_ISSUE_ERROR = 'RECEIVE_UPDATE_ISSUE_ERROR';
|
||||
export const ADD_BOARD_ITEM_TO_LIST = 'ADD_BOARD_ITEM_TO_LIST';
|
||||
export const REMOVE_BOARD_ITEM_FROM_LIST = 'REMOVE_BOARD_ITEM_FROM_LIST';
|
||||
export const SET_CURRENT_PAGE = 'SET_CURRENT_PAGE';
|
||||
export const TOGGLE_EMPTY_STATE = 'TOGGLE_EMPTY_STATE';
|
||||
export const SET_ACTIVE_ID = 'SET_ACTIVE_ID';
|
||||
export const UPDATE_BOARD_ITEM_BY_ID = 'UPDATE_BOARD_ITEM_BY_ID';
|
||||
export const SET_ASSIGNEE_LOADING = 'SET_ASSIGNEE_LOADING';
|
||||
|
|
|
@ -6,11 +6,6 @@ import { formatIssue } from '../boards_util';
|
|||
import { issuableTypes } from '../constants';
|
||||
import * as mutationTypes from './mutation_types';
|
||||
|
||||
const notImplemented = () => {
|
||||
/* eslint-disable-next-line @gitlab/require-i18n-strings */
|
||||
throw new Error('Not implemented!');
|
||||
};
|
||||
|
||||
const updateListItemsCount = ({ state, listId, value }) => {
|
||||
const list = state.boardLists[listId];
|
||||
if (state.issuableType === issuableTypes.epic) {
|
||||
|
@ -94,18 +89,10 @@ export default {
|
|||
state.error = s__('Boards|An error occurred while generating lists. Please reload the page.');
|
||||
},
|
||||
|
||||
[mutationTypes.REQUEST_ADD_LIST]: () => {
|
||||
notImplemented();
|
||||
},
|
||||
|
||||
[mutationTypes.RECEIVE_ADD_LIST_SUCCESS]: (state, list) => {
|
||||
Vue.set(state.boardLists, list.id, list);
|
||||
},
|
||||
|
||||
[mutationTypes.RECEIVE_ADD_LIST_ERROR]: () => {
|
||||
notImplemented();
|
||||
},
|
||||
|
||||
[mutationTypes.MOVE_LIST]: (state, { movedList, listAtNewIndex }) => {
|
||||
const { boardLists } = state;
|
||||
Vue.set(boardLists, movedList.id, movedList);
|
||||
|
@ -172,35 +159,11 @@ export default {
|
|||
state.isSettingAssignees = isLoading;
|
||||
},
|
||||
|
||||
[mutationTypes.REQUEST_ADD_ISSUE]: () => {
|
||||
notImplemented();
|
||||
},
|
||||
|
||||
[mutationTypes.RECEIVE_ADD_ISSUE_SUCCESS]: () => {
|
||||
notImplemented();
|
||||
},
|
||||
|
||||
[mutationTypes.RECEIVE_ADD_ISSUE_ERROR]: () => {
|
||||
notImplemented();
|
||||
},
|
||||
|
||||
[mutationTypes.MUTATE_ISSUE_SUCCESS]: (state, { issue }) => {
|
||||
const issueId = getIdFromGraphQLId(issue.id);
|
||||
Vue.set(state.boardItems, issueId, formatIssue({ ...issue, id: issueId }));
|
||||
},
|
||||
|
||||
[mutationTypes.REQUEST_UPDATE_ISSUE]: () => {
|
||||
notImplemented();
|
||||
},
|
||||
|
||||
[mutationTypes.RECEIVE_UPDATE_ISSUE_SUCCESS]: () => {
|
||||
notImplemented();
|
||||
},
|
||||
|
||||
[mutationTypes.RECEIVE_UPDATE_ISSUE_ERROR]: () => {
|
||||
notImplemented();
|
||||
},
|
||||
|
||||
[mutationTypes.ADD_BOARD_ITEM_TO_LIST]: (
|
||||
state,
|
||||
{ itemId, listId, moveBeforeId, moveAfterId, atIndex },
|
||||
|
@ -220,14 +183,6 @@ export default {
|
|||
Vue.delete(state.boardItems, itemId);
|
||||
},
|
||||
|
||||
[mutationTypes.SET_CURRENT_PAGE]: () => {
|
||||
notImplemented();
|
||||
},
|
||||
|
||||
[mutationTypes.TOGGLE_EMPTY_STATE]: () => {
|
||||
notImplemented();
|
||||
},
|
||||
|
||||
[mutationTypes.REQUEST_GROUP_PROJECTS]: (state, fetchNext) => {
|
||||
Vue.set(state, 'groupProjectsFlags', {
|
||||
[fetchNext ? 'isLoadingMore' : 'isLoading']: true,
|
||||
|
|
|
@ -33,6 +33,10 @@ const focusFirstInvalidInput = (e) => {
|
|||
}
|
||||
};
|
||||
|
||||
const getInputElement = (el) => {
|
||||
return el.querySelector('input') || el;
|
||||
};
|
||||
|
||||
const isEveryFieldValid = (form) => Object.values(form.fields).every(({ state }) => state === true);
|
||||
|
||||
const createValidator = (context, feedbackMap) => ({ el, reportInvalidInput = false }) => {
|
||||
|
@ -91,8 +95,9 @@ export default function initValidation(customFeedbackMap = {}) {
|
|||
const elDataMap = new WeakMap();
|
||||
|
||||
return {
|
||||
inserted(el, binding, { context }) {
|
||||
inserted(element, binding, { context }) {
|
||||
const { arg: showGlobalValidation } = binding;
|
||||
const el = getInputElement(element);
|
||||
const { form: formEl } = el;
|
||||
|
||||
const validate = createValidator(context, feedbackMap);
|
||||
|
@ -121,7 +126,8 @@ export default function initValidation(customFeedbackMap = {}) {
|
|||
|
||||
validate({ el, reportInvalidInput: showGlobalValidation });
|
||||
},
|
||||
update(el, binding) {
|
||||
update(element, binding) {
|
||||
const el = getInputElement(element);
|
||||
const { arg: showGlobalValidation } = binding;
|
||||
const { validate, isTouched, isBlurred } = elDataMap.get(el);
|
||||
const showValidationFeedback = showGlobalValidation || (isTouched && isBlurred);
|
||||
|
|
|
@ -103,7 +103,7 @@ module Issuable
|
|||
end
|
||||
scope :assigned_to, ->(u) do
|
||||
assignees_table = Arel::Table.new("#{to_ability_name}_assignees")
|
||||
sql = assignees_table.project('true').where(assignees_table[:user_id].in(u)).where(Arel::Nodes::SqlLiteral.new("#{to_ability_name}_id = #{to_ability_name}s.id"))
|
||||
sql = assignees_table.project('true').where(assignees_table[:user_id].in(u.id)).where(Arel::Nodes::SqlLiteral.new("#{to_ability_name}_id = #{to_ability_name}s.id"))
|
||||
where("EXISTS (#{sql.to_sql})")
|
||||
end
|
||||
# rubocop:enable GitlabSecurity/SqlInjection
|
||||
|
|
|
@ -381,7 +381,7 @@ class MergeRequest < ApplicationRecord
|
|||
scope :review_requested_to, ->(user) do
|
||||
where(
|
||||
reviewers_subquery
|
||||
.where(Arel::Table.new("#{to_ability_name}_reviewers")[:user_id].eq(user))
|
||||
.where(Arel::Table.new("#{to_ability_name}_reviewers")[:user_id].eq(user.id))
|
||||
.exists
|
||||
)
|
||||
end
|
||||
|
@ -389,7 +389,7 @@ class MergeRequest < ApplicationRecord
|
|||
scope :no_review_requested_to, ->(user) do
|
||||
where(
|
||||
reviewers_subquery
|
||||
.where(Arel::Table.new("#{to_ability_name}_reviewers")[:user_id].eq(user))
|
||||
.where(Arel::Table.new("#{to_ability_name}_reviewers")[:user_id].eq(user.id))
|
||||
.exists
|
||||
.not
|
||||
)
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
-# Shortcut to Project > Activity
|
||||
%li.hidden
|
||||
= link_to activity_project_path(@project), title: _('Activity'), class: 'shortcuts-project-activity' do
|
||||
%span
|
||||
= _('Activity')
|
||||
|
||||
-# Shortcut to Repository > Graph (formerly, Network)
|
||||
- if project_nav_tab? :network
|
||||
%li.hidden
|
||||
= link_to project_network_path(@project, current_ref), title: _('Network'), class: 'shortcuts-network' do
|
||||
= _('Graph')
|
||||
|
||||
-# Shortcut to Issues > New Issue
|
||||
- if project_nav_tab?(:issues)
|
||||
%li.hidden
|
||||
= link_to new_project_issue_path(@project), class: 'shortcuts-new-issue' do
|
||||
= _('Create a new issue')
|
||||
|
||||
-# Shortcut to Pipelines > Jobs
|
||||
- if project_nav_tab? :builds
|
||||
%li.hidden
|
||||
= link_to project_jobs_path(@project), title: _('Jobs'), class: 'shortcuts-builds' do
|
||||
= _('Jobs')
|
||||
|
||||
-# Shortcut to commits page
|
||||
- if project_nav_tab? :commits
|
||||
%li.hidden
|
||||
= link_to project_commits_path(@project), title: _('Commits'), class: 'shortcuts-commits' do
|
||||
= _('Commits')
|
||||
|
||||
-# Shortcut to issue boards
|
||||
- if project_nav_tab?(:issues)
|
||||
%li.hidden
|
||||
= link_to _('Issue Boards'), project_boards_path(@project), title: _('Issue Boards'), class: 'shortcuts-issue-boards'
|
|
@ -11,4 +11,5 @@
|
|||
- if sidebar.render_raw_menus_partial
|
||||
= render sidebar.render_raw_menus_partial
|
||||
|
||||
= render partial: 'shared/nav/sidebar_hidden_menu_item', collection: sidebar.hidden_menu&.items
|
||||
= render 'shared/sidebar_toggle_button'
|
||||
|
|
3
app/views/shared/nav/_sidebar_hidden_menu_item.html.haml
Normal file
3
app/views/shared/nav/_sidebar_hidden_menu_item.html.haml
Normal file
|
@ -0,0 +1,3 @@
|
|||
%li.hidden
|
||||
= link_to sidebar_hidden_menu_item.link, **sidebar_hidden_menu_item.container_html_options do
|
||||
= sidebar_hidden_menu_item.title
|
|
@ -9,6 +9,8 @@ class RemoveDuplicateLabelsFromProject < ActiveRecord::Migration[6.0]
|
|||
disable_ddl_transaction!
|
||||
|
||||
class BackupLabel < Label
|
||||
self.inheritance_column = :_type_disabled
|
||||
|
||||
self.table_name = 'backup_labels'
|
||||
end
|
||||
|
||||
|
|
|
@ -537,7 +537,7 @@ inactive_users.each do |user|
|
|||
end
|
||||
```
|
||||
|
||||
### Find Max permissions for project/group
|
||||
### Find a user's max permissions for project/group
|
||||
|
||||
```ruby
|
||||
user = User.find_by_username 'username'
|
||||
|
|
|
@ -190,7 +190,7 @@ GET /users
|
|||
]
|
||||
```
|
||||
|
||||
Users on GitLab [Premium or higher](https://about.gitlab.com/pricing/) also see the `shared_runners_minutes_limit`, `extra_shared_runners_minutes_limit`, and `using_license_seat` parameters.
|
||||
Users on GitLab [Premium or higher](https://about.gitlab.com/pricing/) also see the `shared_runners_minutes_limit`, `extra_shared_runners_minutes_limit`, `is_auditor`, and `using_license_seat` parameters.
|
||||
|
||||
```json
|
||||
[
|
||||
|
@ -199,6 +199,7 @@ Users on GitLab [Premium or higher](https://about.gitlab.com/pricing/) also see
|
|||
...
|
||||
"shared_runners_minutes_limit": 133,
|
||||
"extra_shared_runners_minutes_limit": 133,
|
||||
"is_auditor": false,
|
||||
"using_license_seat": true
|
||||
...
|
||||
}
|
||||
|
@ -359,12 +360,13 @@ NOTE:
|
|||
The `plan` and `trial` parameters are only available on GitLab Enterprise Edition.
|
||||
|
||||
Users on GitLab [Premium or higher](https://about.gitlab.com/pricing/) also see
|
||||
the `shared_runners_minutes_limit`, and `extra_shared_runners_minutes_limit` parameters.
|
||||
the `shared_runners_minutes_limit`, `is_auditor`, and `extra_shared_runners_minutes_limit` parameters.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"username": "john_smith",
|
||||
"is_auditor": false,
|
||||
"shared_runners_minutes_limit": 133,
|
||||
"extra_shared_runners_minutes_limit": 133,
|
||||
...
|
||||
|
@ -628,6 +630,8 @@ GET /user
|
|||
}
|
||||
```
|
||||
|
||||
Users on GitLab [Premium or higher](https://about.gitlab.com/pricing/) also see the `shared_runners_minutes_limit`, `extra_shared_runners_minutes_limit`, `is_auditor`, and `using_license_seat` parameters.
|
||||
|
||||
## User status
|
||||
|
||||
Get the status of the currently signed in user.
|
||||
|
|
|
@ -52,11 +52,9 @@ the `author` field. GitLab team members **should not**.
|
|||
a changelog entry regardless of these guidelines if the contributor wants one.
|
||||
Example: "Fixed a typo on the search results page."
|
||||
- Any docs-only changes **should not** have a changelog entry.
|
||||
- Any change behind a feature flag **disabled** by default **should not** have a changelog entry.
|
||||
- Any change behind a feature flag that is **enabled** by default **should** have a changelog entry.
|
||||
- For changes related to feature flags, use [feature flag guide](feature_flags/index.md#changelog) to determine the changelog entry.
|
||||
- Any change that adds new Usage Data metrics, sets the status of existing ones to `removed`, and changes that need to be documented in Product Intelligence [Metrics Dictionary](usage_ping/dictionary.md) **should** have a changelog entry.
|
||||
- A change that adds snowplow events **should** have a changelog entry -
|
||||
- A change that [removes a feature flag, or removes a feature and its feature flag](feature_flags/index.md) **must** have a changelog entry.
|
||||
- A fix for a regression introduced and then fixed in the same release (i.e.,
|
||||
fixing a bug introduced during a monthly release candidate) **should not**
|
||||
have a changelog entry.
|
||||
|
|
|
@ -15,12 +15,6 @@ This document provides guidelines on how to use feature flags
|
|||
in the GitLab codebase to conditionally enable features
|
||||
and test them.
|
||||
|
||||
Features that are developed and merged behind a feature flag
|
||||
should not include a changelog entry. A changelog entry with `type: added` should be included in the merge
|
||||
request removing the feature flag or the merge request where the default value of
|
||||
the feature flag is set to enabled. If the feature contains any database migrations, it
|
||||
*should* include a changelog entry for the database changes.
|
||||
|
||||
WARNING:
|
||||
All newly-introduced feature flags should be [disabled by default](https://about.gitlab.com/handbook/product-development-flow/feature-flag-lifecycle/#feature-flags-in-gitlab-development).
|
||||
|
||||
|
@ -55,7 +49,7 @@ should be leveraged:
|
|||
a specific project and ensure that there are no issues with the implementation.
|
||||
1. When the feature is ready to be announced, create a merge request that adds
|
||||
documentation about the feature, including [documentation for the feature flag itself](../documentation/feature_flags.md),
|
||||
and a changelog entry. In the same merge request either flip the feature flag to
|
||||
and a [changelog entry](#changelog). In the same merge request either flip the feature flag to
|
||||
be **on by default** or remove it entirely in order to enable the new behavior.
|
||||
|
||||
One might be tempted to think that feature flags will delay the release of a
|
||||
|
@ -461,6 +455,24 @@ as follows:
|
|||
Feature.remove(:feature_flag_name)
|
||||
```
|
||||
|
||||
## Changelog
|
||||
|
||||
- Any change behind a feature flag **disabled** by default **should not** have a changelog entry.
|
||||
- **Exception:** database migrations **should** have a changelog entry.
|
||||
- Any change related to a feature flag itself (flag removal, default-on setting) **should** have a changelog entry.
|
||||
Use the flowchart to determine the changelog entry type.
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[flag: default off] -->|'added' / 'changed'| B(flag: default on)
|
||||
B -->|'other'| C(remove flag, keep new code)
|
||||
B -->|'removed' / 'changed'| D(remove flag, keep old code)
|
||||
A -->|'added' / 'changed'| C
|
||||
A -->|no changelog| D
|
||||
```
|
||||
|
||||
- Any change behind a feature flag that is **enabled** by default **should** have a changelog entry.
|
||||
|
||||
## Feature flags in tests
|
||||
|
||||
Introducing a feature flag into the codebase creates an additional code path that should be tested.
|
||||
|
|
|
@ -569,7 +569,10 @@ To get you started quickly, GitLab provides the configuration file
|
|||
[`gitlab-api-fuzzing-config.yml`](https://gitlab.com/gitlab-org/security-products/analyzers/api-fuzzing/-/blob/master/gitlab-api-fuzzing-config.yml).
|
||||
This file has several testing profiles that perform various numbers of tests. The run time of each
|
||||
profile increases as the test numbers go up. To use a configuration file, add it to your
|
||||
repository's root as `.gitlab-api-fuzzing.yml`.
|
||||
repository as `.gitlab/gitlab-api-fuzzing-config.yml`.
|
||||
|
||||
NOTE:
|
||||
+In GitLab 13.11 and earlier, the configuration file was `.gitlab-api-fuzzing.yml` in the repository's root. In GitLab 13.12 and later, it is `.gitlab/gitlab-api-fuzzing-config.yml` in the repository's root.
|
||||
|
||||
| Profile | Fuzz Tests (per parameter) |
|
||||
|:---------|:-----------|
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 7.7 KiB |
Binary file not shown.
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 68 KiB |
|
@ -150,10 +150,8 @@ the following:
|
|||
|
||||
![Security Center Dashboard with projects](img/security_center_dashboard_v13_4.png)
|
||||
|
||||
You can access the Security Center from the menu
|
||||
bar at the top of the page. Under **More**, select **Security**.
|
||||
|
||||
![Security Center navigation link](img/security_center_dashboard_link_v12_4.png)
|
||||
To view the Security Center, from the navigation bar at the top of the page, select
|
||||
**More > Security**.
|
||||
|
||||
### Adding projects to the Security Center
|
||||
|
||||
|
|
95
lib/sidebars/projects/menus/hidden_menu.rb
Normal file
95
lib/sidebars/projects/menus/hidden_menu.rb
Normal file
|
@ -0,0 +1,95 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Sidebars
|
||||
module Projects
|
||||
module Menus
|
||||
class HiddenMenu < ::Sidebars::Menu
|
||||
override :configure_menu_items
|
||||
def configure_menu_items
|
||||
add_item(activity_menu_item)
|
||||
add_item(graph_menu_item)
|
||||
add_item(new_issue_menu_item)
|
||||
add_item(jobs_menu_item)
|
||||
add_item(commits_menu_item)
|
||||
add_item(issue_boards_menu_item)
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def activity_menu_item
|
||||
::Sidebars::MenuItem.new(
|
||||
title: _('Activity'),
|
||||
link: activity_project_path(context.project),
|
||||
active_routes: {},
|
||||
container_html_options: { class: 'shortcuts-project-activity' },
|
||||
item_id: :activity
|
||||
)
|
||||
end
|
||||
|
||||
def graph_menu_item
|
||||
return unless can?(context.current_user, :download_code, context.project)
|
||||
return if context.project.empty_repo?
|
||||
|
||||
::Sidebars::MenuItem.new(
|
||||
title: _('Graph'),
|
||||
link: project_network_path(context.project, context.current_ref),
|
||||
active_routes: {},
|
||||
container_html_options: { class: 'shortcuts-network' },
|
||||
item_id: :graph
|
||||
)
|
||||
end
|
||||
|
||||
def new_issue_menu_item
|
||||
return unless can?(context.current_user, :read_issue, context.project)
|
||||
|
||||
::Sidebars::MenuItem.new(
|
||||
title: _('Create a new issue'),
|
||||
link: new_project_issue_path(context.project),
|
||||
active_routes: {},
|
||||
container_html_options: { class: 'shortcuts-new-issue' },
|
||||
item_id: :new_issue
|
||||
)
|
||||
end
|
||||
|
||||
def jobs_menu_item
|
||||
return unless can?(context.current_user, :read_build, context.project)
|
||||
|
||||
::Sidebars::MenuItem.new(
|
||||
title: _('Jobs'),
|
||||
link: project_jobs_path(context.project),
|
||||
active_routes: {},
|
||||
container_html_options: { class: 'shortcuts-builds' },
|
||||
item_id: :jobs
|
||||
)
|
||||
end
|
||||
|
||||
def commits_menu_item
|
||||
return unless can?(context.current_user, :download_code, context.project)
|
||||
return if context.project.empty_repo?
|
||||
|
||||
::Sidebars::MenuItem.new(
|
||||
title: _('Commits'),
|
||||
link: project_commits_path(context.project),
|
||||
active_routes: {},
|
||||
container_html_options: { class: 'shortcuts-commits' },
|
||||
item_id: :commits
|
||||
)
|
||||
end
|
||||
|
||||
def issue_boards_menu_item
|
||||
return unless can?(context.current_user, :read_issue, context.project)
|
||||
|
||||
::Sidebars::MenuItem.new(
|
||||
title: _('Issue Boards'),
|
||||
link: project_boards_path(context.project),
|
||||
active_routes: {},
|
||||
container_html_options: { class: 'shortcuts-issue-boards' },
|
||||
item_id: :issue_boards
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,6 +6,7 @@ module Sidebars
|
|||
override :configure_menus
|
||||
def configure_menus
|
||||
set_scope_menu(Sidebars::Projects::Menus::ScopeMenu.new(context))
|
||||
set_hidden_menu(Sidebars::Projects::Menus::HiddenMenu.new(context))
|
||||
|
||||
add_menu(Sidebars::Projects::Menus::ProjectInformationMenu.new(context))
|
||||
add_menu(Sidebars::Projects::Menus::LearnGitlabMenu.new(context))
|
||||
|
@ -27,11 +28,6 @@ module Sidebars
|
|||
add_menu(Sidebars::Projects::Menus::SettingsMenu.new(context))
|
||||
end
|
||||
|
||||
override :render_raw_menus_partial
|
||||
def render_raw_menus_partial
|
||||
'layouts/nav/sidebar/project_menus'
|
||||
end
|
||||
|
||||
override :aria_label
|
||||
def aria_label
|
||||
_('Project navigation')
|
||||
|
|
|
@ -10199,9 +10199,6 @@ msgstr ""
|
|||
msgid "DastProfiles|Password form field"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Please enter a valid timeout value"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Profile name"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -40,12 +40,6 @@ import {
|
|||
|
||||
jest.mock('~/flash');
|
||||
|
||||
const expectNotImplemented = (action) => {
|
||||
it('is not implemented', () => {
|
||||
expect(action).toThrow(new Error('Not implemented!'));
|
||||
});
|
||||
};
|
||||
|
||||
// We need this helper to make sure projectPath is including
|
||||
// subgroups when the movIssue action is called.
|
||||
const getProjectPath = (path) => path.split('#')[0];
|
||||
|
@ -1825,27 +1819,3 @@ describe('unsetError', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchBacklog', () => {
|
||||
expectNotImplemented(actions.fetchBacklog);
|
||||
});
|
||||
|
||||
describe('bulkUpdateIssues', () => {
|
||||
expectNotImplemented(actions.bulkUpdateIssues);
|
||||
});
|
||||
|
||||
describe('fetchIssue', () => {
|
||||
expectNotImplemented(actions.fetchIssue);
|
||||
});
|
||||
|
||||
describe('toggleIssueSubscription', () => {
|
||||
expectNotImplemented(actions.toggleIssueSubscription);
|
||||
});
|
||||
|
||||
describe('showPage', () => {
|
||||
expectNotImplemented(actions.showPage);
|
||||
});
|
||||
|
||||
describe('toggleEmptyState', () => {
|
||||
expectNotImplemented(actions.toggleEmptyState);
|
||||
});
|
||||
|
|
|
@ -13,12 +13,6 @@ import {
|
|||
mockList,
|
||||
} from '../mock_data';
|
||||
|
||||
const expectNotImplemented = (action) => {
|
||||
it('is not implemented', () => {
|
||||
expect(action).toThrow(new Error('Not implemented!'));
|
||||
});
|
||||
};
|
||||
|
||||
describe('Board Store Mutations', () => {
|
||||
let state;
|
||||
|
||||
|
@ -158,10 +152,6 @@ describe('Board Store Mutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('REQUEST_ADD_LIST', () => {
|
||||
expectNotImplemented(mutations.REQUEST_ADD_LIST);
|
||||
});
|
||||
|
||||
describe('RECEIVE_ADD_LIST_SUCCESS', () => {
|
||||
it('adds list to boardLists state', () => {
|
||||
mutations.RECEIVE_ADD_LIST_SUCCESS(state, mockLists[0]);
|
||||
|
@ -172,10 +162,6 @@ describe('Board Store Mutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('RECEIVE_ADD_LIST_ERROR', () => {
|
||||
expectNotImplemented(mutations.RECEIVE_ADD_LIST_ERROR);
|
||||
});
|
||||
|
||||
describe('MOVE_LIST', () => {
|
||||
it('updates boardLists state with reordered lists', () => {
|
||||
state = {
|
||||
|
@ -341,10 +327,6 @@ describe('Board Store Mutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('REQUEST_ADD_ISSUE', () => {
|
||||
expectNotImplemented(mutations.REQUEST_ADD_ISSUE);
|
||||
});
|
||||
|
||||
describe('UPDATE_BOARD_ITEM_BY_ID', () => {
|
||||
const issueId = '1';
|
||||
const prop = 'id';
|
||||
|
@ -386,14 +368,6 @@ describe('Board Store Mutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('RECEIVE_ADD_ISSUE_SUCCESS', () => {
|
||||
expectNotImplemented(mutations.RECEIVE_ADD_ISSUE_SUCCESS);
|
||||
});
|
||||
|
||||
describe('RECEIVE_ADD_ISSUE_ERROR', () => {
|
||||
expectNotImplemented(mutations.RECEIVE_ADD_ISSUE_ERROR);
|
||||
});
|
||||
|
||||
describe('MUTATE_ISSUE_SUCCESS', () => {
|
||||
it('updates issue in issues state', () => {
|
||||
const issues = {
|
||||
|
@ -434,18 +408,6 @@ describe('Board Store Mutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('REQUEST_UPDATE_ISSUE', () => {
|
||||
expectNotImplemented(mutations.REQUEST_UPDATE_ISSUE);
|
||||
});
|
||||
|
||||
describe('RECEIVE_UPDATE_ISSUE_SUCCESS', () => {
|
||||
expectNotImplemented(mutations.RECEIVE_UPDATE_ISSUE_SUCCESS);
|
||||
});
|
||||
|
||||
describe('RECEIVE_UPDATE_ISSUE_ERROR', () => {
|
||||
expectNotImplemented(mutations.RECEIVE_UPDATE_ISSUE_ERROR);
|
||||
});
|
||||
|
||||
describe('ADD_BOARD_ITEM_TO_LIST', () => {
|
||||
beforeEach(() => {
|
||||
setBoardsListsState();
|
||||
|
@ -540,14 +502,6 @@ describe('Board Store Mutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('SET_CURRENT_PAGE', () => {
|
||||
expectNotImplemented(mutations.SET_CURRENT_PAGE);
|
||||
});
|
||||
|
||||
describe('TOGGLE_EMPTY_STATE', () => {
|
||||
expectNotImplemented(mutations.TOGGLE_EMPTY_STATE);
|
||||
});
|
||||
|
||||
describe('REQUEST_GROUP_PROJECTS', () => {
|
||||
it('Should set isLoading in groupProjectsFlags to true in state when fetchNext is false', () => {
|
||||
mutations[types.REQUEST_GROUP_PROJECTS](state, false);
|
||||
|
|
|
@ -4,12 +4,18 @@ import validation from '~/vue_shared/directives/validation';
|
|||
describe('validation directive', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = ({ inputAttributes, showValidation } = {}) => {
|
||||
const createComponent = ({ inputAttributes, showValidation, template } = {}) => {
|
||||
const defaultInputAttributes = {
|
||||
type: 'text',
|
||||
required: true,
|
||||
};
|
||||
|
||||
const defaultTemplate = `
|
||||
<form>
|
||||
<input v-validation:[showValidation] name="exampleField" v-bind="attributes" />
|
||||
</form>
|
||||
`;
|
||||
|
||||
const component = {
|
||||
directives: {
|
||||
validation: validation(),
|
||||
|
@ -29,11 +35,7 @@ describe('validation directive', () => {
|
|||
},
|
||||
};
|
||||
},
|
||||
template: `
|
||||
<form>
|
||||
<input v-validation:[showValidation] name="exampleField" v-bind="attributes" />
|
||||
</form>
|
||||
`,
|
||||
template: template || defaultTemplate,
|
||||
};
|
||||
|
||||
wrapper = shallowMount(component, { attachTo: document.body });
|
||||
|
@ -48,6 +50,12 @@ describe('validation directive', () => {
|
|||
const findForm = () => wrapper.find('form');
|
||||
const findInput = () => wrapper.find('input');
|
||||
|
||||
const setValueAndTriggerValidation = (value) => {
|
||||
const input = findInput();
|
||||
input.setValue(value);
|
||||
input.trigger('blur');
|
||||
};
|
||||
|
||||
describe.each([true, false])(
|
||||
'with fields untouched and "showValidation" set to "%s"',
|
||||
(showValidation) => {
|
||||
|
@ -78,12 +86,6 @@ describe('validation directive', () => {
|
|||
`(
|
||||
'with input-attributes set to $inputAttributes',
|
||||
({ inputAttributes, validValue, invalidValue }) => {
|
||||
const setValueAndTriggerValidation = (value) => {
|
||||
const input = findInput();
|
||||
input.setValue(value);
|
||||
input.trigger('blur');
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent({ inputAttributes });
|
||||
});
|
||||
|
@ -129,4 +131,52 @@ describe('validation directive', () => {
|
|||
});
|
||||
},
|
||||
);
|
||||
|
||||
describe('with group elements', () => {
|
||||
const template = `
|
||||
<form>
|
||||
<div v-validation:[showValidation]>
|
||||
<input name="exampleField" v-bind="attributes" />
|
||||
</div>
|
||||
</form>
|
||||
`;
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
template,
|
||||
inputAttributes: {
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('with invalid value', () => {
|
||||
beforeEach(() => {
|
||||
setValueAndTriggerValidation('');
|
||||
});
|
||||
|
||||
it('should set correct field state', () => {
|
||||
expect(getFormData().fields.exampleField).toEqual({
|
||||
state: false,
|
||||
feedback: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('should set correct feedback', () => {
|
||||
expect(getFormData().fields.exampleField.feedback).toBe('Please fill out this field.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with valid value', () => {
|
||||
beforeEach(() => {
|
||||
setValueAndTriggerValidation('hello');
|
||||
});
|
||||
|
||||
it('set the correct state', () => {
|
||||
expect(getFormData().fields.exampleField).toEqual({
|
||||
state: true,
|
||||
feedback: '',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
102
spec/lib/sidebars/projects/menus/hidden_menu_spec.rb
Normal file
102
spec/lib/sidebars/projects/menus/hidden_menu_spec.rb
Normal file
|
@ -0,0 +1,102 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Sidebars::Projects::Menus::HiddenMenu do
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
|
||||
let(:user) { project.owner }
|
||||
let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project, current_ref: project.repository.root_ref) }
|
||||
|
||||
describe '#render?' do
|
||||
subject { described_class.new(context) }
|
||||
|
||||
context 'when menu does not have any menu items' do
|
||||
it 'returns false' do
|
||||
allow(subject).to receive(:has_items?).and_return(false)
|
||||
|
||||
expect(subject.render?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when menu has menu items' do
|
||||
it 'returns true' do
|
||||
expect(subject.render?).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Menu items' do
|
||||
subject { described_class.new(context).items.index { |e| e.item_id == item_id } }
|
||||
|
||||
shared_examples 'access rights checks' do
|
||||
specify { is_expected.not_to be_nil }
|
||||
|
||||
describe 'when the user does not have access' do
|
||||
let(:user) { nil }
|
||||
|
||||
specify { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Activity' do
|
||||
let(:item_id) { :activity }
|
||||
|
||||
context 'when user has access to the project' do
|
||||
specify { is_expected.not_to be_nil }
|
||||
|
||||
describe 'when the user is not present' do
|
||||
let(:user) { nil }
|
||||
|
||||
specify { is_expected.not_to be_nil }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Graph' do
|
||||
let(:item_id) { :graph }
|
||||
|
||||
context 'when project repository is empty' do
|
||||
before do
|
||||
allow(project).to receive(:empty_repo?).and_return(true)
|
||||
end
|
||||
|
||||
specify { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
it_behaves_like 'access rights checks'
|
||||
end
|
||||
|
||||
describe 'New Issue' do
|
||||
let(:item_id) { :new_issue }
|
||||
|
||||
it_behaves_like 'access rights checks'
|
||||
end
|
||||
|
||||
describe 'Jobs' do
|
||||
let(:item_id) { :jobs }
|
||||
|
||||
it_behaves_like 'access rights checks'
|
||||
end
|
||||
|
||||
describe 'Commits' do
|
||||
let(:item_id) { :commits }
|
||||
|
||||
context 'when project repository is empty' do
|
||||
before do
|
||||
allow(project).to receive(:empty_repo?).and_return(true)
|
||||
end
|
||||
|
||||
specify { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
it_behaves_like 'access rights checks'
|
||||
end
|
||||
|
||||
describe 'Issue Boards' do
|
||||
let(:item_id) { :issue_boards }
|
||||
|
||||
it_behaves_like 'access rights checks'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1090,5 +1090,43 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'Hidden menus' do
|
||||
it 'has a link to the Activity page' do
|
||||
render
|
||||
|
||||
expect(rendered).to have_link('Activity', href: activity_project_path(project), class: 'shortcuts-project-activity', visible: false)
|
||||
end
|
||||
|
||||
it 'has a link to the Graph page' do
|
||||
render
|
||||
|
||||
expect(rendered).to have_link('Graph', href: project_network_path(project, current_ref), class: 'shortcuts-network', visible: false)
|
||||
end
|
||||
|
||||
it 'has a link to the New Issue page' do
|
||||
render
|
||||
|
||||
expect(rendered).to have_link('Create a new issue', href: new_project_issue_path(project), class: 'shortcuts-new-issue', visible: false)
|
||||
end
|
||||
|
||||
it 'has a link to the Jobs page' do
|
||||
render
|
||||
|
||||
expect(rendered).to have_link('Jobs', href: project_jobs_path(project), class: 'shortcuts-builds', visible: false)
|
||||
end
|
||||
|
||||
it 'has a link to the Commits page' do
|
||||
render
|
||||
|
||||
expect(rendered).to have_link('Commits', href: project_commits_path(project), class: 'shortcuts-commits', visible: false)
|
||||
end
|
||||
|
||||
it 'has a link to the Issue Boards page' do
|
||||
render
|
||||
|
||||
expect(rendered).to have_link('Issue Boards', href: project_boards_path(project), class: 'shortcuts-issue-boards', visible: false)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'sidebar includes snowplow attributes', 'render', 'projects_side_navigation', 'projects_side_navigation'
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue