Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-05-07 18:10:21 +00:00
parent 05b83be3ee
commit 1572e2a376
57 changed files with 1044 additions and 713 deletions

View File

@ -1,11 +1,11 @@
<!-- Title suggestion: [Feature flag] Enable description of feature -->
## Feature
## Summary
This feature uses the `:feature_name` feature flag!
This issue is to rollout [the feature](ISSUE LINK) on production,
that is currently behind the `<feature-flag-name>` feature flag.
<!-- Short description of what the feature is about and link to relevant other issues. -->
- [Issue Name](ISSUE LINK)
## Owners
@ -26,14 +26,15 @@ Are there any other stages or teams involved that need to be kept in the loop?
## The Rollout Plan
- Partial Rollout on GitLab.com with beta groups
- Partial Rollout on GitLab.com with testing groups
- Rollout on GitLab.com for a certain period (How long)
- Percentage Rollout on GitLab.com
- Rollout Feature for everyone as soon as it's ready
<!-- Which dashboards from https://dashboards.gitlab.net are most relevant? Sentry errors reports can also be useful to review -->
**Beta Groups/Projects:**
## Testing Groups/Projects/Users
<!-- If applicable, any groups/projects that are happy to have this feature turned on early. Some organizations may wish to test big changes they are interested in with a small subset of users ahead of time for example. -->
- `gitlab-org/gitlab` project
@ -55,54 +56,97 @@ Are there any other stages or teams involved that need to be kept in the loop?
<!-- Which dashboards from https://dashboards.gitlab.net are most relevant? -->
## Rollout Timeline
## Rollout Steps
<!-- Please check which steps are needed and remove those which don't apply -->
### Rollout on non-production environments
**Rollout Steps**
- [ ] Ensure that the feature MRs have been deployed to non-production environments.
- [ ] `/chatops run auto_deploy status <merge-commit-of-your-feature>`
- [ ] Enable the feature globally on non-production environments.
- [ ] `/chatops run feature set <feature-flag-name> true --dev`
- [ ] `/chatops run feature set <feature-flag-name> true --staging`
- [ ] Verify that the feature works as expected. Posting the QA result in this issue is preferable.
*Preparation Phase*
- [ ] Enable on staging (`/chatops run feature set feature_name true --staging`)
- [ ] Test on staging
- [ ] Ensure that documentation has been updated ([More info](https://docs.gitlab.com/ee/development/documentation/feature_flags.html#features-that-became-enabled-by-default))
- [ ] Announce on the issue an estimated time this will be enabled on GitLab.com
### Preparation before production rollout
- [ ] Ensure that the feature MRs have been deployed to both production and canary.
- [ ] `/chatops run auto_deploy status <merge-commit-of-your-feature>`
- [ ] Check if the feature flag change needs to be accompanied with a
[change management
issue](https://about.gitlab.com/handbook/engineering/infrastructure/change-management/#feature-flags-and-the-change-management-process). Cross
link the issue here if it does.
[change management issue](https://about.gitlab.com/handbook/engineering/infrastructure/change-management/#feature-flags-and-the-change-management-process).
Cross link the issue here if it does.
- [ ] Ensure that you or a representative in development can be available for at least 2 hours after feature flag updates in production.
If a different developer will be covering, or an exception is needed, please inform the oncall SRE by using the `@sre-oncall` Slack alias.
- [ ] Ensure that documentation has been updated ([More info](https://docs.gitlab.com/ee/development/documentation/feature_flags.html#features-that-became-enabled-by-default)).
- [ ] Announce on [the feature issue](ISSUE LINK) an estimated time this will be enabled on GitLab.com.
- [ ] If the feature flag in code has [an actor](https://docs.gitlab.com/ee/development/feature_flags/#feature-actors), enable it on GitLab.com for [testing groups/projects](#testing-groupsprojectsusers).
- [ ] `/chatops run feature set --<actor-type>=<actor> <feature-flag-name> true`
- [ ] Verify that the feature works as expected. Posting the QA result in this issue is preferable.
- [ ] Ensure that you or a representative in development can be available for at least 2 hours after feature flag updates in production. If a different developer will be covering, or an exception is needed, please inform the oncall SRE by using the `@sre-oncall` Slack alias.
### Global rollout on production
*Partial Rollout Phase*
- [ ] [Incrementally roll out](https://docs.gitlab.com/ee/development/feature_flags/controls.html#process) the feature. If there is no risk that the feature affects usability or server loads, skip to the global rollout.
- If the feature flag in code has [an actor](https://docs.gitlab.com/ee/development/feature_flags/#feature-actors), perform **actor-based** rollout.
- [ ] `/chatops run feature set <feature-flag-name> <rollout-percentage> --actors`
- If the feature flag in code does **NOT** have [an actor](https://docs.gitlab.com/ee/development/feature_flags/#feature-actors), perform time-based rollout (**random** rollout).
- [ ] `/chatops run feature set <feature-flag-name> <rollout-percentage>`
- [ ] Enable the feature globally on production environment.
- [ ] `/chatops run feature set <feature-flag-name> true`
- [ ] Announce on [the feature issue](ISSUE LINK) that the feature has been globally enabled.
- [ ] Cross-post chatops slack command to `#support_gitlab-com`.
([more guidance when this is necessary in the dev docs](https://docs.gitlab.com/ee/development/feature_flags/controls.html#communicate-the-change)) and in your team channel
- [ ] Wait for [at least one day for the verification term](https://about.gitlab.com/handbook/product-development-flow/feature-flag-lifecycle/#including-a-feature-behind-feature-flag-in-the-final-release).
- [ ] Enable on GitLab.com for individual groups/projects listed above and verify behaviour (`/chatops run feature set --project=gitlab-org/gitlab feature_name true`)
### (Optional) Release the feature with the feature flag
- [ ] Verify behaviour (See Beta Groups) and add details with screenshots as a comment on this issue
If you're still unsure whether the feature is [deemed stable](https://about.gitlab.com/handbook/product-development-flow/feature-flag-lifecycle/#including-a-feature-behind-feature-flag-in-the-final-release)
but want to release it in the current milestone, you can change the default state of the feature flag to be enabled.
To do so, follow these steps:
- [ ] If it is possible to perform an incremental rollout, this should be preferred. Proposed increments are: `10%`, `50%`, `100%`. Proposed minimum time between increments is 15 minutes.
- When setting percentages, make sure that the feature works correctly between feature checks. See https://gitlab.com/gitlab-org/gitlab/-/issues/327117 for more information
- For actor-based rollout: `/chatops run feature set feature_name 10 --actors`
- For time-based rollout: `/chatops run feature set feature_name 10`
- [ ] Create a merge request with the following changes. Ask for review and merge it.
- [ ] Set the `default_enabled` attribute in [the feature flag definition](https://docs.gitlab.com/ee/development/feature_flags/#feature-flag-definition-and-validation) to `true`.
- [ ] Create [a changelog entry](https://docs.gitlab.com/ee/development/feature_flags/#changelog).
- [ ] Ensure that the above MR has been deployed to both production and canary.
If the merge request was deployed before [the code cutoff](https://about.gitlab.com/handbook/engineering/releases/#self-managed-releases-1),
the feature can be officially announced in a release blog post.
- [ ] `/chatops run auto_deploy status <merge-commit>`
- [ ] Close [the feature issue](ISSUE LINK) to indicate the feature has been released.
*Full Rollout Phase*
- [ ] Make the feature flag enabled by default i.e. Change `default_enabled` to `true`
**WARNING:** This approach has the downside that it makes it difficult for us to
[clean up](https://docs.gitlab.com/ee/development/feature_flags/controls.html#cleaning-up) the flag.
For example, on-premise users could disable the feature on their GitLab instance. But when you
remove the flag at some point, they suddenly see the feature as enabled and they can't roll it back
to the previous behavior. To avoid this potential breaking change, use this approach only for urgent
matters.
- [ ] Cross post chatops slack command to `#support_gitlab-com` ([more guidance when this is necessary in the dev docs](https://docs.gitlab.com/ee/development/feature_flags/controls.html#where-to-run-commands)) and in your team channel
### Release the feature
- [ ] Announce on the issue that the flag has been enabled
After the feature has been [deemed stable](https://about.gitlab.com/handbook/product-development-flow/feature-flag-lifecycle/#including-a-feature-behind-feature-flag-in-the-final-release),
the [clean up](https://docs.gitlab.com/ee/development/feature_flags/controls.html#cleaning-up)
should be done as soon as possible to permanently enable the feature and reduce complexity in the
codebase.
- [ ] Create a cleanup issue using the "Feature Flag Removal" template
<!-- The checklist here is to help stakeholders keep track of the feature flag status -->
- [ ] Create a merge request to remove `<feature-flag-name>` feature flag. Ask for review and merge it.
- [ ] Remove all references to the feature flag from the codebase.
- [ ] Remove the YAML definitions for the feature from the repository.
- [ ] Create [a changelog entry](https://docs.gitlab.com/ee/development/feature_flags/#changelog).
- [ ] Ensure that the above MR has been deployed to both production and canary.
If the merge request was deployed before [the code cutoff](https://about.gitlab.com/handbook/engineering/releases/#self-managed-releases-1),
the feature can be officially announced in a release blog post.
- [ ] `/chatops run auto_deploy status <merge-commit>`
- [ ] Close [the feature issue](ISSUE LINK) to indicate the feature has been released.
- [ ] Clean up the feature flag from all environments by running these chatops command in `#production` channel:
- [ ] `/chatops run feature delete <feature-flag-name> --dev`
- [ ] `/chatops run feature delete <feature-flag-name> --staging`
- [ ] `/chatops run feature delete <feature-flag-name>`
- [ ] Close this rollout issue.
## Rollback Steps
- [ ] This feature can be disabled by running the following Chatops command:
```
/chatops run feature set --project=gitlab-org/gitlab feature_name false
/chatops run feature set <feature-flag-name> false
```
/label ~"feature flag"

View File

@ -300,7 +300,7 @@ gem 'gon', '~> 6.4.0'
gem 'request_store', '~> 1.5'
gem 'base32', '~> 0.3.0'
gem "gitlab-license", "~> 1.4"
gem 'gitlab-license', '~> 1.5'
# Protect against bruteforcing
gem 'rack-attack', '~> 6.3.0'

View File

@ -475,7 +475,7 @@ GEM
opentracing (~> 0.4)
pg_query (~> 1.3)
redis (> 3.0.0, < 5.0.0)
gitlab-license (1.4.0)
gitlab-license (1.5.0)
gitlab-mail_room (0.0.9)
gitlab-markup (1.7.1)
gitlab-net-dns (0.9.1)
@ -1455,7 +1455,7 @@ DEPENDENCIES
gitlab-fog-azure-rm (~> 1.0.1)
gitlab-fog-google (~> 1.13)
gitlab-labkit (~> 0.16.2)
gitlab-license (~> 1.4)
gitlab-license (~> 1.5)
gitlab-mail_room (~> 0.0.9)
gitlab-markup (~> 1.7.1)
gitlab-net-dns (~> 0.9.1)

View File

@ -375,7 +375,7 @@ export const MR_PREVIOUS_FILE_IN_DIFF = {
export const MR_GO_TO_FILE = {
id: 'mergeRequests.goToFile',
description: __('Go to file'),
defaultKeys: ['t', 'mod+p'],
defaultKeys: ['mod+p', 't'],
customizable: false,
};

View File

@ -0,0 +1,80 @@
<script>
import { __, s__ } from '~/locale';
// Map some keys to their proper representation depending on the system
// See also: https://craig.is/killing/mice#keys
const getKeyMap = () => {
const keyMap = {
up: '↑',
down: '↓',
left: '←',
right: '→',
ctrl: s__('KeyboardKey|Ctrl'),
shift: s__('KeyboardKey|Shift'),
enter: s__('KeyboardKey|Enter'),
esc: s__('KeyboardKey|Esc'),
command: '⌘',
option: window.gl?.client?.isMac ? '⌥' : s__('KeyboardKey|Alt'),
};
// Meta and alt are aliases
keyMap.meta = keyMap.command;
keyMap.alt = keyMap.option;
// Mod is Command on Mac, and Ctrl on Windows/Linux
keyMap.mod = window.gl?.client?.isMac ? keyMap.command : keyMap.ctrl;
return keyMap;
};
export default {
functional: true,
props: {
shortcuts: {
type: Array,
required: true,
},
},
render(createElement, context) {
const keyMap = getKeyMap();
const { staticClass } = context.data;
const shortcuts = context.props.shortcuts.reduce((acc, shortcut, i) => {
if (
!window.gl?.client?.isMac &&
(shortcut.includes('command') || shortcut.includes('meta'))
) {
return acc;
}
const keys = shortcut.split(/([ +])/);
if (i !== 0 && acc.length) {
acc.push(` ${__('or')} `);
// If there are multiple alternative shortcuts,
// we keep them on the same line if they are single-key, e.g. `]` or `j`
// but if they consist of multiple keys, we insert a line break, e.g.:
// `shift` + `]` <br> or `shift` + `j`
if (keys.length > 1) {
acc.push(createElement('br'));
}
}
keys.forEach((key) => {
if (key === '+') {
acc.push(' + ');
} else if (key === ' ') {
acc.push(` ${__('then')} `);
} else {
acc.push(createElement('kbd', {}, [keyMap[key] ?? key]));
}
});
return acc;
}, []);
return createElement('div', { staticClass }, shortcuts);
},
};
</script>

View File

@ -1,525 +1,99 @@
<script>
/* eslint-disable @gitlab/vue-require-i18n-strings */
import { GlIcon, GlModal } from '@gitlab/ui';
import { GlModal, GlSearchBoxByType } from '@gitlab/ui';
import { s__, __ } from '~/locale';
import { keybindingGroups } from './keybindings';
import Shortcut from './shortcut.vue';
import ShortcutsToggle from './shortcuts_toggle.vue';
export default {
components: {
GlIcon,
GlModal,
GlSearchBoxByType,
ShortcutsToggle,
Shortcut,
},
data() {
return {
searchTerm: '',
};
},
computed: {
ctrlCharacter() {
return window.gl.client.isMac ? '⌘' : 'ctrl';
},
onDotCom() {
return window.gon.dot_com;
filteredKeybindings() {
if (!this.searchTerm) {
return keybindingGroups;
}
const search = this.searchTerm.toLocaleLowerCase();
const mapped = keybindingGroups.map((group) => {
if (group.name.toLocaleLowerCase().includes(search)) {
return group;
}
return {
...group,
keybindings: group.keybindings.filter((binding) =>
binding.description.toLocaleLowerCase().includes(search),
),
};
});
return mapped.filter((group) => group.keybindings.length);
},
},
i18n: {
title: __(`Keyboard shortcuts`),
search: s__(`KeyboardShortcuts|Search keyboard shortcuts`),
noMatch: s__(`KeyboardShortcuts|No shortcuts matched your search`),
},
};
</script>
<template>
<gl-modal
modal-id="keyboard-shortcut-modal"
size="lg"
:title="$options.i18n.title"
data-testid="modal-shortcuts"
body-class="shortcut-help-body gl-p-0!"
:visible="true"
:hide-footer="true"
@hidden="$emit('hidden')"
>
<template #modal-title>
<shortcuts-toggle />
</template>
<div class="row">
<div class="col-lg-4">
<table class="shortcut-mappings text-2">
<tbody>
<tr>
<th></th>
<th>{{ __('Global Shortcuts') }}</th>
</tr>
<tr>
<td class="shortcut">
<kbd>?</kbd>
</td>
<td>{{ __('Toggle this dialog') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>shift p</kbd>
</td>
<td>{{ __('Go to your projects') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>shift g</kbd>
</td>
<td>{{ __('Go to your groups') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>shift a</kbd>
</td>
<td>{{ __('Go to the activity feed') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>shift l</kbd>
</td>
<td>{{ __('Go to the milestone list') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>shift s</kbd>
</td>
<td>{{ __('Go to your snippets') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>s</kbd>
/
<kbd>/</kbd>
</td>
<td>{{ __('Start search') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>shift i</kbd>
</td>
<td>{{ __('Go to your issues') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>shift m</kbd>
</td>
<td>{{ __('Go to your merge requests') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>shift t</kbd>
</td>
<td>{{ __('Go to your To-Do list') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>p</kbd>
<kbd>b</kbd>
</td>
<td>{{ __('Toggle the Performance Bar') }}</td>
</tr>
<tr v-if="onDotCom">
<td class="shortcut">
<kbd>g</kbd>
<kbd>x</kbd>
</td>
<td>{{ __('Toggle GitLab Next') }}</td>
</tr>
</tbody>
<tbody>
<tr>
<th></th>
<th>{{ __('Editing') }}</th>
</tr>
<tr>
<td class="shortcut">
<kbd>{{ ctrlCharacter }} shift p</kbd>
</td>
<td>{{ __('Toggle Markdown preview') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>
<gl-icon name="arrow-up" />
</kbd>
</td>
<td>
{{ __('Edit your most recent comment in a thread (from an empty textarea)') }}
</td>
</tr>
</tbody>
<tbody>
<tr>
<th></th>
<th>{{ __('Wiki') }}</th>
</tr>
<tr>
<td class="shortcut">
<kbd>e</kbd>
</td>
<td>{{ __('Edit wiki page') }}</td>
</tr>
</tbody>
<tbody>
<tr>
<th></th>
<th>{{ __('Repository Graph') }}</th>
</tr>
<tr>
<td class="shortcut">
<kbd>
<gl-icon name="arrow-left" />
</kbd>
/
<kbd>h</kbd>
</td>
<td>{{ __('Scroll left') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>
<gl-icon name="arrow-right" />
</kbd>
/
<kbd>l</kbd>
</td>
<td>{{ __('Scroll right') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>
<gl-icon name="arrow-up" />
</kbd>
/
<kbd>k</kbd>
</td>
<td>{{ __('Scroll up') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>
<gl-icon name="arrow-down" />
</kbd>
/
<kbd>j</kbd>
</td>
<td>{{ __('Scroll down') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>
shift
<gl-icon name="arrow-up" />
/ k
</kbd>
</td>
<td>{{ __('Scroll to top') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>
shift
<gl-icon name="arrow-down" />
/ j
</kbd>
</td>
<td>{{ __('Scroll to bottom') }}</td>
</tr>
</tbody>
</table>
</div>
<div class="col-lg-4">
<table class="shortcut-mappings text-2">
<tbody>
<tr>
<th></th>
<th>{{ __('Project') }}</th>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>p</kbd>
</td>
<td>{{ __("Go to the project's overview page") }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>v</kbd>
</td>
<td>{{ __("Go to the project's activity feed") }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>r</kbd>
</td>
<td>{{ __('Go to releases') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>f</kbd>
</td>
<td>{{ __('Go to files') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>t</kbd>
</td>
<td>{{ __('Go to find file') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>c</kbd>
</td>
<td>{{ __('Go to commits') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>n</kbd>
</td>
<td>{{ __('Go to repository graph') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>d</kbd>
</td>
<td>{{ __('Go to repository charts') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>i</kbd>
</td>
<td>{{ __('Go to issues') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>i</kbd>
</td>
<td>{{ __('New issue') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>b</kbd>
</td>
<td>{{ __('Go to issue boards') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>m</kbd>
</td>
<td>{{ __('Go to merge requests') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>j</kbd>
</td>
<td>{{ __('Go to jobs') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>l</kbd>
</td>
<td>{{ __('Go to metrics') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>e</kbd>
</td>
<td>{{ __('Go to environments') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>k</kbd>
</td>
<td>{{ __('Go to kubernetes') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>s</kbd>
</td>
<td>{{ __('Go to snippets') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>g</kbd>
<kbd>w</kbd>
</td>
<td>{{ __('Go to wiki') }}</td>
</tr>
</tbody>
<tbody>
<tr>
<th></th>
<th>{{ __('Project Files') }}</th>
</tr>
<tr>
<td class="shortcut">
<kbd>
<gl-icon name="arrow-up" />
</kbd>
</td>
<td>{{ __('Move selection up') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>
<gl-icon name="arrow-down" />
</kbd>
</td>
<td>{{ __('Move selection down') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>enter</kbd>
</td>
<td>{{ __('Open Selection') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>esc</kbd>
</td>
<td>{{ __('Go back (while searching for files)') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>y</kbd>
</td>
<td>{{ __('Go to file permalink (while viewing a file)') }}</td>
</tr>
</tbody>
</table>
</div>
<div class="col-lg-4">
<table class="shortcut-mappings text-2">
<tbody>
<tr>
<th></th>
<th>{{ __('Epics, issues, and merge requests') }}</th>
</tr>
<tr>
<td class="shortcut">
<kbd>r</kbd>
</td>
<td>{{ __('Comment/Reply (quoting selected text)') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>e</kbd>
</td>
<td>{{ __('Edit description') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>l</kbd>
</td>
<td>{{ __('Change label') }}</td>
</tr>
</tbody>
<tbody>
<tr>
<th></th>
<th>{{ __('Issues and merge requests') }}</th>
</tr>
<tr>
<td class="shortcut">
<kbd>a</kbd>
</td>
<td>{{ __('Change assignee') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>m</kbd>
</td>
<td>{{ __('Change milestone') }}</td>
</tr>
</tbody>
<tbody>
<tr>
<th></th>
<th>{{ __('Merge requests') }}</th>
</tr>
<tr>
<td class="shortcut">
<kbd>]</kbd>
/
<kbd>j</kbd>
</td>
<td>{{ __('Next file in diff') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>[</kbd>
/
<kbd>k</kbd>
</td>
<td>{{ __('Previous file in diff') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>{{ ctrlCharacter }} p</kbd>
</td>
<td>{{ __('Go to file') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>n</kbd>
</td>
<td>{{ __('Next unresolved discussion') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>p</kbd>
</td>
<td>{{ __('Previous unresolved discussion') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>b</kbd>
</td>
<td>{{ __('Copy source branch name') }}</td>
</tr>
</tbody>
<tbody>
<tr>
<th></th>
<th>{{ __('Merge request commits') }}</th>
</tr>
<tr>
<td class="shortcut">
<kbd>c</kbd>
</td>
<td>{{ __('Next commit') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>x</kbd>
</td>
<td>{{ __('Previous commit') }}</td>
</tr>
</tbody>
<tbody>
<tr>
<th></th>
<th>{{ __('Web IDE') }}</th>
</tr>
<tr>
<td class="shortcut">
<kbd>{{ ctrlCharacter }} p</kbd>
</td>
<td>{{ __('Go to file') }}</td>
</tr>
<tr>
<td class="shortcut">
<kbd>{{ ctrlCharacter }} enter</kbd>
</td>
<td>{{ __('Commit (when editing commit message)') }}</td>
</tr>
</tbody>
</table>
</div>
<div
class="gl-sticky gl-top-0 gl-py-5 gl-px-5 gl-display-flex gl-align-items-center gl-bg-white"
>
<gl-search-box-by-type
v-model.trim="searchTerm"
:aria-label="$options.i18n.search"
class="gl-w-half gl-mr-3"
/>
<shortcuts-toggle class="gl-w-half gl-ml-3" />
</div>
<div v-if="filteredKeybindings.length === 0" class="gl-px-5">
{{ $options.i18n.noMatch }}
</div>
<div v-else class="shortcut-help-container gl-mt-8 gl-px-5 gl-pb-5">
<section
v-for="group in filteredKeybindings"
:key="group.id"
class="shortcut-help-mapping gl-mb-4"
>
<strong class="shortcut-help-mapping-title gl-w-half gl-display-inline-block">
{{ group.name }}
</strong>
<div
v-for="keybinding in group.keybindings"
:key="keybinding.id"
class="gl-display-flex gl-align-items-center"
>
<shortcut
class="gl-w-40p gl-flex-shrink-0 gl-text-right gl-pr-4"
:shortcuts="keybinding.defaultKeys"
/>
<div class="gl-w-half gl-flex-shrink-0 gl-flex-grow-1">
{{ keybinding.description }}
</div>
</div>
</section>
</div>
</gl-modal>
</template>

View File

@ -6,7 +6,7 @@ import { disableShortcuts, enableShortcuts, shouldDisableShortcuts } from './sho
export default {
i18n: {
toggleLabel: __('Keyboard shortcuts'),
toggleLabel: __('Toggle shortcuts'),
},
components: {
GlToggle,
@ -31,14 +31,12 @@ export default {
</script>
<template>
<div v-if="localStorageUsable" class="d-inline-flex align-items-center js-toggle-shortcuts">
<div v-if="localStorageUsable" class="js-toggle-shortcuts">
<gl-toggle
v-model="shortcutsEnabled"
aria-describedby="shortcutsToggle"
:label="$options.i18n.toggleLabel"
label-position="left"
@change="onChange"
/>
<div id="shortcutsToggle" class="sr-only">{{ __('Enable or disable keyboard shortcuts') }}</div>
</div>
</template>

View File

@ -7,6 +7,8 @@ const createAnchor = (href) => {
const fragment = new DocumentFragment();
const el = document.createElement('a');
el.classList.add('link-anchor');
el.setAttribute('data-qa-selector', 'line_link');
el.setAttribute('data-qa-number', href);
el.href = href;
fragment.appendChild(el);
el.addEventListener('contextmenu', (e) => {

View File

@ -105,10 +105,6 @@ hr {
}
}
kbd {
display: inline-block;
}
code {
padding: 2px 4px;
color: $code-color;

View File

@ -22,6 +22,7 @@
@import 'framework/flash';
@import 'framework/forms';
@import 'framework/gfm';
@import 'framework/kbd';
@import 'framework/header';
@import 'framework/highlight';
@import 'framework/issue_box';

View File

@ -266,15 +266,6 @@
}
}
.shortcut-mappings {
display: none;
}
&.shortcuts .shortcut-mappings {
display: inline-block;
margin-right: 5px;
}
ul {
margin: 0;
padding: 0;

View File

@ -0,0 +1,15 @@
kbd {
display: inline-block;
padding: 3px 5px;
font-size: $gl-font-size-monospace-sm;
line-height: 10px;
color: var(--gray-700, $gray-700);
vertical-align: middle;
background-color: var(--gray-10, $gray-10);
border-width: 1px;
border-style: solid;
border-color: var(--gray-100, $gray-100) var(--gray-100, $gray-100) var(--gray-200, $gray-200);
border-image: none;
border-radius: 3px;
box-shadow: 0 -1px 0 var(--gray-200, $gray-200) inset;
}

View File

@ -81,22 +81,6 @@
word-break: keep-all;
}
kbd {
display: inline-block;
padding: 3px 5px;
font-size: 11px;
line-height: 10px;
color: $gray-700;
vertical-align: middle;
background-color: $gray-10;
border-width: 1px;
border-style: solid;
border-color: $gray-100 $gray-100 $gray-200;
border-image: none;
border-radius: 3px;
box-shadow: 0 -1px 0 $gray-200 inset;
}
h1 {
font-size: 1.75em;
font-weight: $gl-font-weight-bold;

View File

@ -1,30 +1,30 @@
.shortcut-mappings {
font-size: 12px;
color: $gray-700;
tbody:first-child tr:first-child {
padding-top: 0;
.shortcut-help {
&-body {
height: 80vh;
overflow-y: scroll;
}
th {
padding-top: 15px;
line-height: 1.5;
color: $help-shortcut-header-color;
text-align: left;
&-container {
column-count: 1;
@include media-breakpoint-up(md) {
column-count: 2;
}
column-gap: 1rem;
}
td {
padding-top: 3px;
padding-bottom: 3px;
vertical-align: top;
line-height: 20px;
}
&-mapping {
overflow: hidden;
break-inside: avoid;
.shortcut {
padding-right: 10px;
color: $gray-300;
text-align: right;
white-space: nowrap;
&-title {
margin-left: 40%;
}
kbd {
margin: 0.1rem 0;
line-height: unset;
font-size: unset;
}
}
}

View File

@ -10,9 +10,9 @@ module Repositories
end
def execute
return unless search
return unless search && offset && limit
repository.search_branch_names(search)
repository.search_branch_names(search).lazy.drop(offset).take(limit) # rubocop:disable CodeReuse/ActiveRecord
end
private
@ -20,5 +20,13 @@ module Repositories
def search
@params[:search].presence
end
def offset
@params[:offset]
end
def limit
@params[:limit]
end
end
end

View File

@ -10,8 +10,16 @@ module Resolvers
required: true,
description: 'The pattern to search for branch names by.'
def resolve(search_pattern:)
Repositories::BranchNamesFinder.new(object, search: search_pattern).execute
argument :offset, GraphQL::INT_TYPE,
required: true,
description: 'The number of branch names to skip.'
argument :limit, GraphQL::INT_TYPE,
required: true,
description: 'The number of branch names to return.'
def resolve(search_pattern:, offset:, limit:)
Repositories::BranchNamesFinder.new(object, offset: offset, limit: limit, search: search_pattern).execute
end
end
end

View File

@ -28,7 +28,6 @@ module OptimizedIssuableLabelFilter
# Taken from IssuableFinder
def count_by_state
return super if root_namespace.nil?
return super if Feature.disabled?(:optimized_issuable_label_filter, default_enabled: :yaml)
count_params = params.merge(state: nil, sort: nil, force_cte: true)
@ -40,7 +39,11 @@ module OptimizedIssuableLabelFilter
.group(:state_id)
.count
counts = state_counts.transform_keys { |key| count_key(key) }
counts = Hash.new(0)
state_counts.each do |key, value|
counts[count_key(key)] += value
end
counts[:all] = counts.values.sum
counts.with_indifferent_access

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
module Users
class UpsertCreditCardValidationService < BaseService
def initialize(params)
@params = params.to_h.with_indifferent_access
end
def execute
::Users::CreditCardValidation.upsert(@params)
ServiceResponse.success(message: 'CreditCardValidation was set')
rescue ActiveRecord::InvalidForeignKey, ActiveRecord::NotNullViolation => e
ServiceResponse.error(message: "Could not set CreditCardValidation: #{e.message}")
rescue StandardError => e
Gitlab::ErrorTracking.track_exception(e, params: @params, class: self.class.to_s)
ServiceResponse.error(message: "Could not set CreditCardValidation: #{e.message}")
end
end
end

View File

@ -0,0 +1,5 @@
---
title: Add migration to update plans on new post-EoA subscriptions
merge_request: 55625
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Add API to set credit card validation timestamp for user
merge_request: 60828
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Fix incorrect issue and merge requests counts with filters
merge_request: 61230
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: "Update Keyboard shortcut help: adding search, update styling"
merge_request: 56400
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Add offset and limit to branch names resolver.
merge_request: 61061
author:
type: changed

View File

@ -1,18 +1,19 @@
---
key_path: counts.boards
description: Count of total Boards created
description: Count of Boards created
product_section: dev
product_stage: plan
product_group: group::project management
product_category: boards
product_category: boards
value_type: number
status: data_available
time_frame: all
data_source: database
instrumentation_class: 'Gitlab::Usage::Metrics::Instrumentations::CountBoardsMetric'
distribution:
- ce
- ee
- ee
tier:
- free
- premium
- ultimate
- premium
- ultimate

View File

@ -0,0 +1,54 @@
# frozen_string_literal: true
class UpdateGitlabSubscriptionsStartAtPostEoa < ActiveRecord::Migration[6.0]
UPDATE_BATCH_SIZE = 100
disable_ddl_transaction!
class Plan < ActiveRecord::Base
self.table_name = 'plans'
self.inheritance_column = :_type_disabled
end
class GitlabSubscription < ActiveRecord::Base
include EachBatch
self.table_name = 'gitlab_subscriptions'
self.inheritance_column = :_type_disabled
EOA_ROLLOUT_DATE = '2021-01-26'
scope :with_plan, -> (from_plan) do
where("start_date >= ? AND hosted_plan_id = ?", EOA_ROLLOUT_DATE, from_plan.id)
end
end
def up
return unless Gitlab.com?
silver_plan = Plan.find_by(name: 'silver')
gold_plan = Plan.find_by(name: 'gold')
premium_plan = Plan.find_by(name: 'premium')
ultimate_plan = Plan.find_by(name: 'ultimate')
# Silver to Premium
update_hosted_plan_for_subscription(from_plan: silver_plan, to_plan: premium_plan)
# Gold to Ultimate
update_hosted_plan_for_subscription(from_plan: gold_plan, to_plan: ultimate_plan)
end
def down
# no-op
end
private
def update_hosted_plan_for_subscription(from_plan:, to_plan:)
return unless from_plan && to_plan
GitlabSubscription.with_plan(from_plan).each_batch(of: UPDATE_BATCH_SIZE) do |batch|
batch.update_all(hosted_plan_id: to_plan.id)
end
end
end

View File

@ -0,0 +1 @@
cef2421a6885cb8b28d34388af6c79c4be1564dfd5fae2efcb35622d511eb8c0

View File

@ -74,8 +74,6 @@ The following metrics are available:
| `gitlab_transaction_event_import_repository_total` | Counter | 9.4 | Counter for repository imports (RepositoryImportWorker) | |
| `gitlab_transaction_event_patch_hard_limit_bytes_hit_total` | Counter | 13.9 | Counter for diff patch size limit hits | |
| `gitlab_transaction_event_push_branch_total` | Counter | 9.4 | Counter for all branch pushes | |
| `gitlab_transaction_event_push_commit_total` | Counter | 9.4 | Counter for commits | `branch` |
| `gitlab_transaction_event_push_tag_total` | Counter | 9.4 | Counter for tag pushes | |
| `gitlab_transaction_event_rails_exception_total` | Counter | 9.4 | Counter for number of rails exceptions | |
| `gitlab_transaction_event_receive_email_total` | Counter | 9.4 | Counter for received emails | `handler` |
| `gitlab_transaction_event_remote_mirrors_failed_total` | Counter | 10.8 | Counter for failed remote mirrors | |

View File

@ -11760,6 +11760,8 @@ Returns [`[String!]`](#string).
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="repositorybranchnameslimit"></a>`limit` | [`Int!`](#int) | The number of branch names to return. |
| <a id="repositorybranchnamesoffset"></a>`offset` | [`Int!`](#int) | The number of branch names to skip. |
| <a id="repositorybranchnamessearchpattern"></a>`searchPattern` | [`String!`](#string) | The pattern to search for branch names by. |
##### `Repository.tree`

View File

@ -118,7 +118,7 @@ job with the same name, the job artifact from the parent pipeline is returned.
## Access the latest job artifacts by URL
You can download the latest job artifacts by using a URL.
You can download job artifacts from the latest successful pipeline by using a URL.
To download the whole artifacts archive:

View File

@ -468,7 +468,7 @@ Tiers: `free`
### `counts.boards`
Count of total Boards created
Count of Boards created
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216181252_boards.yml)

View File

@ -4,7 +4,7 @@ group: Optimize
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Analytics
# Analytics **(FREE)**
## Definitions
@ -70,38 +70,38 @@ in one place.
[Learn more about instance-level analytics](../admin_area/analytics/index.md).
## Group-level analytics
## Group-level analytics **(PREMIUM)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/195979) in GitLab 12.8.
> - Moved to [GitLab Premium](https://about.gitlab.com/pricing/) due to Starter/Bronze being [discontinued](https://about.gitlab.com/blog/2021/01/26/new-gitlab-product-subscription-model/) in 13.9.
> - Moved to GitLab Premium in 13.9.
The following analytics features are available at the group level:
- [Application Security](../application_security/security_dashboard/#group-security-dashboard). **(ULTIMATE)**
- [Contribution](../group/contribution_analytics/index.md). **(PREMIUM)**
- [DevOps Adoption](../group/devops_adoption/index.md). **(ULTIMATE)**
- [Insights](../group/insights/index.md). **(ULTIMATE)**
- [Issue](../group/issues_analytics/index.md). **(PREMIUM)**
- [Productivity](productivity_analytics.md). **(PREMIUM)**
- [Repositories](../group/repositories_analytics/index.md). **(PREMIUM)**
- [Value Stream](../group/value_stream_analytics/index.md). **(PREMIUM)**
- [Application Security](../application_security/security_dashboard/#group-security-dashboard)
- [Contribution](../group/contribution_analytics/index.md)
- [DevOps Adoption](../group/devops_adoption/index.md)
- [Insights](../group/insights/index.md)
- [Issue](../group/issues_analytics/index.md)
- [Productivity](productivity_analytics.md)
- [Repositories](../group/repositories_analytics/index.md)
- [Value Stream](../group/value_stream_analytics/index.md)
## Project-level analytics
The following analytics features are available at the project level:
- [Application Security](../application_security/security_dashboard/#project-security-dashboard). **(ULTIMATE)**
- [CI/CD](ci_cd_analytics.md). **(FREE)**
- [Code Review](code_review_analytics.md). **(PREMIUM)**
- [Insights](../project/insights/index.md). **(ULTIMATE)**
- [Issue](../group/issues_analytics/index.md). **(PREMIUM)**
- [Application Security](../application_security/security_dashboard/#project-security-dashboard)
- [CI/CD](ci_cd_analytics.md)
- [Code Review](code_review_analytics.md)
- [Insights](../project/insights/index.md)
- [Issue](../group/issues_analytics/index.md)
- [Merge Request](merge_request_analytics.md), enabled with the `project_merge_request_analytics`
[feature flag](../../development/feature_flags/index.md#enabling-a-feature-flag-locally-in-development). **(PREMIUM)**
- [Repository](repository_analytics.md). **(FREE)**
- [Value Stream](value_stream_analytics.md). **(FREE)**
[feature flag](../../development/feature_flags/index.md#enabling-a-feature-flag-locally-in-development)
- [Repository](repository_analytics.md)
- [Value Stream](value_stream_analytics.md)
## User-configurable analytics
## User-configurable analytics **(ULTIMATE)**
The following analytics features are available for users to create personalized views:
- [Application Security](../application_security/security_dashboard/#security-center). **(ULTIMATE)**
- [Application Security](../application_security/security_dashboard/#security-center)

View File

@ -7,8 +7,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Merge Request Analytics **(PREMIUM)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229045) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.3.
> - Moved to [GitLab Premium](https://about.gitlab.com/pricing/) due to Starter/Bronze being [discontinued](https://about.gitlab.com/blog/2021/01/26/new-gitlab-product-subscription-model/) in 13.9.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229045) in GitLab 13.3.
> - Moved to GitLab Premium in 13.9.
Merge Request Analytics helps you understand the efficiency of your code review process, and the productivity of your team.
@ -56,7 +56,7 @@ The throughput chart shows the number of merge requests merged per month.
### Throughput table
[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/232651) in GitLab 13.3.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/232651) in GitLab 13.3.
The Throughput table displays the most recent merge requests merged in the date range. The
table displays up to 20 merge requests at a time. If there are more than 20 merge requests,

View File

@ -12,22 +12,17 @@ packages, which can be easily consumed as a dependency in downstream projects.
The Package Registry supports the following formats:
<div class="row">
<div class="col-md-9">
<table align="left" style="width:50%">
<tr style="background:#dfdfdf"><th>Package type</th><th>GitLab version</th></tr>
<tr><td><a href="https://docs.gitlab.com/ee/user/packages/composer_repository/index.html">Composer</a></td><td>13.2+</td></tr>
<tr><td><a href="https://docs.gitlab.com/ee/user/packages/conan_repository/index.html">Conan</a></td><td>12.6+</td></tr>
<tr><td><a href="https://docs.gitlab.com/ee/user/packages/go_proxy/index.html">Go</a></td><td>13.1+</td></tr>
<tr><td><a href="https://docs.gitlab.com/ee/user/packages/maven_repository/index.html">Maven</a></td><td>11.3+</td></tr>
<tr><td><a href="https://docs.gitlab.com/ee/user/packages/npm_registry/index.html">npm</a></td><td>11.7+</td></tr>
<tr><td><a href="https://docs.gitlab.com/ee/user/packages/nuget_repository/index.html">NuGet</a></td><td>12.8+</td></tr>
<tr><td><a href="https://docs.gitlab.com/ee/user/packages/pypi_repository/index.html">PyPI</a></td><td>12.10+</td></tr>
<tr><td><a href="https://docs.gitlab.com/ee/user/packages/generic_packages/index.html">Generic packages</a></td><td>13.5+</td></tr>
<tr><td><a href="https://docs.gitlab.com/ee/user/packages/rubygems_registry/index.html">Ruby gems</a></td><td>13.10+</td></tr>
</table>
</div>
</div>
| Package type | GitLab version |
| ------------ | -------------- |
| [Composer](composer_repository/index.md) | 13.2+ |
| [Conan](conan_repository/index.md) | 12.6+ |
| [Go](go_proxy/index.md) | 13.1+ |
| [Maven](maven_repository/index.md) | 11.3+ |
| [npm](npm_registry/index.md) | 11.7+ |
| [NuGet](nuget_repository/index.md) | 12.8+ |
| [PyPI](pypi_repository/index.md) | 12.10+ |
| [Generic packages](generic_packages/index.md) | 13.5+ |
| [Ruby gems](rubygems_registry/index.md) | 13.10+ |
You can also use the [API](../../api/packages.md) to administer the Package Registry.

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
module API
module Entities
class UserCreditCardValidations < Grape::Entity
expose :user_id, :credit_card_validated_at
end
end
end

View File

@ -996,6 +996,30 @@ module API
present paginate(current_user.emails), with: Entities::Email
end
desc "Update a user's credit_card_validation" do
success Entities::UserCreditCardValidations
end
params do
requires :user_id, type: String, desc: 'The ID or username of the user'
requires :credit_card_validated_at, type: DateTime, desc: 'The time when the user\'s credit card was validated'
end
put ":user_id/credit_card_validation", feature_category: :users do
authenticated_as_admin!
user = find_user(params[:user_id])
not_found!('User') unless user
attrs = declared_params(include_missing: false)
service = ::Users::UpsertCreditCardValidationService.new(attrs).execute
if service.success?
present user.credit_card_validation, with: Entities::UserCreditCardValidations
else
render_api_error!('400 Bad Request', 400)
end
end
desc "Update the current user's preferences" do
success Entities::UserPreferences
detail 'This feature was introduced in GitLab 13.10.'

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
module Gitlab
module Usage
module Metrics
module Instrumentations
class CountBoardsMetric < DatabaseMetric
operation :count
relation { Board }
end
end
end
end
end

View File

@ -3,7 +3,7 @@
module Sidebars
module Projects
module Menus
class ProjectOverviewMenu < ::Sidebars::Menu
class ProjectInformationMenu < ::Sidebars::Menu
override :configure_menu_items
def configure_menu_items
add_item(details_menu_item)
@ -32,17 +32,34 @@ module Sidebars
override :title
def title
_('Project overview')
if Feature.enabled?(:sidebar_refactor, context.current_user)
_('Project information')
else
_('Project overview')
end
end
override :sprite_icon
def sprite_icon
'home'
if Feature.enabled?(:sidebar_refactor, context.current_user)
'project'
else
'home'
end
end
override :active_routes
def active_routes
return {} if Feature.disabled?(:sidebar_refactor, context.current_user)
{ path: 'projects#show' }
end
private
def details_menu_item
return if Feature.enabled?(:sidebar_refactor, context.current_user)
::Sidebars::MenuItem.new(
title: _('Details'),
link: project_path(context.project),

View File

@ -7,7 +7,7 @@ module Sidebars
def configure_menus
set_scope_menu(Sidebars::Projects::Menus::ScopeMenu.new(context))
add_menu(Sidebars::Projects::Menus::ProjectOverviewMenu.new(context))
add_menu(Sidebars::Projects::Menus::ProjectInformationMenu.new(context))
add_menu(Sidebars::Projects::Menus::LearnGitlabMenu.new(context))
add_menu(Sidebars::Projects::Menus::RepositoryMenu.new(context))
add_menu(Sidebars::Projects::Menus::IssuesMenu.new(context))

View File

@ -3661,7 +3661,7 @@ msgstr ""
msgid "An error occurred while reordering issues."
msgstr ""
msgid "An error occurred while requesting data from the Jira service"
msgid "An error occurred while requesting data from the Jira service."
msgstr ""
msgid "An error occurred while retrieving calendar activity"
@ -12185,9 +12185,6 @@ msgstr ""
msgid "Enable or disable Seat Link."
msgstr ""
msgid "Enable or disable keyboard shortcuts"
msgstr ""
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
@ -14538,6 +14535,9 @@ msgstr ""
msgid "Geo|%{component} synced"
msgstr ""
msgid "Geo|%{eventId} (%{timeAgo})"
msgstr ""
msgid "Geo|%{itemTitle} checksum progress"
msgstr ""
@ -14616,6 +14616,9 @@ msgstr ""
msgid "Geo|Data type"
msgstr ""
msgid "Geo|Disabled"
msgstr ""
msgid "Geo|Discover GitLab Geo"
msgstr ""
@ -14643,6 +14646,9 @@ msgstr ""
msgid "Geo|Go to the primary site"
msgstr ""
msgid "Geo|Healthy"
msgstr ""
msgid "Geo|If you want to make changes, you must visit the primary site."
msgstr ""
@ -14706,6 +14712,9 @@ msgstr ""
msgid "Geo|Number of %{title}"
msgstr ""
msgid "Geo|Offline"
msgstr ""
msgid "Geo|Pending synchronization"
msgstr ""
@ -14868,6 +14877,12 @@ msgstr ""
msgid "Geo|Undefined"
msgstr ""
msgid "Geo|Unhealthy"
msgstr ""
msgid "Geo|Unknown"
msgstr ""
msgid "Geo|Unknown state"
msgstr ""
@ -18399,7 +18414,7 @@ msgstr ""
msgid "Jira integration not configured."
msgstr ""
msgid "Jira project key is not configured"
msgid "Jira project key is not configured."
msgstr ""
msgid "Jira project: %{importProject}"
@ -18798,9 +18813,30 @@ msgstr ""
msgid "Keyboard shortcuts"
msgstr ""
msgid "KeyboardKey|Alt"
msgstr ""
msgid "KeyboardKey|Ctrl"
msgstr ""
msgid "KeyboardKey|Ctrl+"
msgstr ""
msgid "KeyboardKey|Enter"
msgstr ""
msgid "KeyboardKey|Esc"
msgstr ""
msgid "KeyboardKey|Shift"
msgstr ""
msgid "KeyboardShortcuts|No shortcuts matched your search"
msgstr ""
msgid "KeyboardShortcuts|Search keyboard shortcuts"
msgstr ""
msgid "Keys"
msgstr ""
@ -25162,6 +25198,9 @@ msgstr ""
msgid "Project info:"
msgstr ""
msgid "Project information"
msgstr ""
msgid "Project is required when cluster_type is :project"
msgstr ""
@ -33762,15 +33801,15 @@ msgstr ""
msgid "Toggle project select"
msgstr ""
msgid "Toggle shortcuts"
msgstr ""
msgid "Toggle sidebar"
msgstr ""
msgid "Toggle the Performance Bar"
msgstr ""
msgid "Toggle this dialog"
msgstr ""
msgid "Toggle thread"
msgstr ""
@ -38934,6 +38973,9 @@ msgstr ""
msgid "the wiki"
msgstr ""
msgid "then"
msgstr ""
msgid "this document"
msgstr ""

View File

@ -147,7 +147,7 @@ module QA
end
def open_web_ide!
click_element :web_ide_button
click_element(:web_ide_button)
end
def has_edit_fork_button?

View File

@ -18,8 +18,8 @@ module QA
end
view 'app/assets/javascripts/ide/components/ide_tree.vue' do
element :new_file_button
element :new_directory_button
element :new_file_button, required: true
element :new_directory_button, required: true
end
view 'app/assets/javascripts/ide/components/ide_tree_list.vue' do
@ -108,6 +108,10 @@ module QA
element :file_to_commit_content
end
view 'app/assets/javascripts/editor/extensions/editor_lite_extension_base.js' do
element :line_link
end
def has_file?(file_name)
within_element(:file_list) do
has_element?(:file_name_content, file_name: file_name)
@ -305,6 +309,22 @@ module QA
def switch_to_commit_tab
click_element(:commit_mode_tab)
end
def select_file(file_name)
# wait for the list of files to load
wait_until(reload: true) do
has_element?(:file_name_content, file_name: file_name)
end
click_element(:file_name_content, file_name: file_name)
end
def link_line(line_number)
wait_for_animated_element(:editor_container)
within_element(:editor_container) do
find('.line-numbers', text: line_number).hover
find_element(:line_link, number: "#L#{line_number}")['href'].to_s
end
end
end
end
end

View File

@ -0,0 +1,43 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Create' do
describe 'Link to line in Web IDE' do
let(:user) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) }
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.template_name = 'express'
end
end
before do
Flow::Login.sign_in
end
after do
project.remove_via_api!
end
it 'can link to a specific line of code in Web IDE', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1102' do
project.visit!
Page::Project::Show.perform(&:open_web_ide!)
Page::Project::WebIDE::Edit.perform do |ide|
ide.select_file('app.js')
@link = ide.link_line('26')
end
Flow::Login.sign_in(as: user)
page.visit(@link)
Page::Project::WebIDE::Edit.perform do |ide|
expect(ide).to have_file('app.js')
end
expect(page.driver.current_url).to include('app.js/#L26')
end
end
end
end

View File

@ -378,11 +378,11 @@ RSpec.describe 'Group' do
expect(page).to have_link('Subgroup information')
end
it 'renders project page with the text "Project overview"' do
it 'renders project page with the text "Project information"' do
visit project_path(project)
wait_for_requests
expect(page).to have_link('Project overview')
expect(page).to have_link('Project information')
end
end

View File

@ -3,11 +3,11 @@
require 'spec_helper'
RSpec.describe 'Project active tab' do
let(:user) { create :user }
let(:project) { create(:project, :repository) }
let_it_be(:project) { create(:project, :repository) }
let(:user) { project.owner }
before do
project.add_maintainer(user)
sign_in(user)
end
@ -18,15 +18,28 @@ RSpec.describe 'Project active tab' do
end
context 'on project Home' do
before do
visit project_path(project)
context 'when feature flag :sidebar_refactor is enabled' do
before do
visit project_path(project)
end
it_behaves_like 'page has active tab', 'Project'
end
it_behaves_like 'page has active tab', 'Project'
it_behaves_like 'page has active sub tab', 'Details'
context 'when feature flag :sidebar_refactor is disabled' do
before do
stub_feature_flags(sidebar_refactor: false)
visit project_path(project)
end
it_behaves_like 'page has active tab', 'Project'
it_behaves_like 'page has active sub tab', 'Details'
end
context 'on project Home/Activity' do
before do
visit project_path(project)
click_tab('Activity')
end

View File

@ -86,6 +86,16 @@ RSpec.describe 'Project navbar' do
]
end
let(:project_information_nav_item) do
{
nav_item: _('Project information'),
nav_sub_items: [
_('Activity'),
_('Releases')
]
}
end
before do
stub_feature_flags(sidebar_refactor: true)
stub_config(registry: { enabled: true })

View File

@ -68,14 +68,27 @@ RSpec.describe 'User uses shortcuts', :js do
end
context 'when navigating to the Project pages' do
it 'redirects to the details page' do
it 'redirects to the project page' do
visit project_issues_path(project)
find('body').native.send_key('g')
find('body').native.send_key('p')
expect(page).to have_active_navigation('Project')
expect(page).to have_active_sub_navigation('Details')
end
context 'when feature flag :sidebar_refactor is disabled' do
it 'redirects to the details page' do
stub_feature_flags(sidebar_refactor: false)
visit project_issues_path(project)
find('body').native.send_key('g')
find('body').native.send_key('p')
expect(page).to have_active_navigation('Project')
expect(page).to have_active_sub_navigation('Details')
end
end
it 'redirects to the activity page' do

View File

@ -866,5 +866,36 @@ RSpec.describe MergeRequestsFinder do
end
end
end
describe '#count_by_state' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:labels) { create_list(:label, 2, project: project) }
let_it_be(:merge_requests) { create_list(:merge_request, 4, :unique_branches, author: user, target_project: project, source_project: project, labels: labels) }
before do
project.add_developer(user)
end
context 'when filtering by multiple labels' do
it 'returns the correnct counts' do
counts = described_class.new(user, { label_name: labels.map(&:name) }).count_by_state
expect(counts[:all]).to eq(merge_requests.size)
end
end
context 'when filtering by approved_by_usernames' do
before do
merge_requests.each { |mr| mr.approved_by_users << user }
end
it 'returns the correnct counts' do
counts = described_class.new(user, { approved_by_usernames: [user.username] }).count_by_state
expect(counts[:all]).to eq(merge_requests.size)
end
end
end
end
end

View File

@ -5,21 +5,34 @@ require 'spec_helper'
RSpec.describe Repositories::BranchNamesFinder do
let(:project) { create(:project, :repository) }
let(:branch_names_finder) { described_class.new(project.repository, search: 'conflict-*') }
describe '#execute' do
subject(:execute) { branch_names_finder.execute }
it 'filters branch names' do
expect(execute).to contain_exactly(
'conflict-binary-file',
'conflict-resolvable',
'conflict-contains-conflict-markers',
'conflict-missing-side',
'conflict-start',
'conflict-non-utf8',
'conflict-too-large'
it 'returns all filtered branch names' do
expect(create_branch_names_finder(0, 100).execute).to contain_exactly(
'snippet/edit-file',
'snippet/multiple-files',
'snippet/no-files',
'snippet/rename-and-edit-file',
'snippet/single-file'
)
end
it 'returns a limited number of offset filtered branch names' do
starting_names = create_branch_names_finder(0, 3).execute
offset_names = create_branch_names_finder(3, 2).execute
expect(starting_names.count).to eq(3)
expect(offset_names.count).to eq(2)
expect(offset_names).not_to include(*starting_names)
all_names = create_branch_names_finder(0, 100).execute
expect(all_names).to contain_exactly(*starting_names, *offset_names)
end
private
def create_branch_names_finder(offset, limit)
described_class.new(project.repository, search: 'snippet/*', offset: offset, limit: limit)
end
end
end

View File

@ -0,0 +1,96 @@
import { shallowMount } from '@vue/test-utils';
import Shortcut from '~/behaviors/shortcuts/shortcut.vue';
describe('Shortcut Vue Component', () => {
const render = (shortcuts) => shallowMount(Shortcut, { propsData: { shortcuts } }).html();
afterEach(() => {
delete window.gl.client;
});
describe.each([true, false])('With browser env isMac: %p', (isMac) => {
beforeEach(() => {
window.gl = { client: { isMac } };
});
it.each([
['up', '<kbd>↑</kbd>'],
['down', '<kbd>↓</kbd>'],
['left', '<kbd>←</kbd>'],
['right', '<kbd>→</kbd>'],
['ctrl', '<kbd>Ctrl</kbd>'],
['shift', '<kbd>Shift</kbd>'],
['enter', '<kbd>Enter</kbd>'],
['esc', '<kbd>Esc</kbd>'],
// Some normal ascii letter
['a', '<kbd>a</kbd>'],
// An umlaut letter
['ø', '<kbd>ø</kbd>'],
// A number
['5', '<kbd>5</kbd>'],
])('renders platform agnostic key %p as: %p', (key, rendered) => {
expect(render([key])).toEqual(`<div>${rendered}</div>`);
});
it('renders keys combined with plus ("+") correctly', () => {
expect(render(['shift+a+b+c'])).toEqual(
`<div><kbd>Shift</kbd> + <kbd>a</kbd> + <kbd>b</kbd> + <kbd>c</kbd></div>`,
);
});
it('renders keys combined with space (" ") correctly', () => {
expect(render(['shift a b c'])).toEqual(
`<div><kbd>Shift</kbd> then <kbd>a</kbd> then <kbd>b</kbd> then <kbd>c</kbd></div>`,
);
});
it('renders multiple shortcuts correctly', () => {
expect(render(['shift+[', 'shift+k'])).toEqual(
`<div><kbd>Shift</kbd> + <kbd>[</kbd> or <br><kbd>Shift</kbd> + <kbd>k</kbd></div>`,
);
expect(render(['[', 'k'])).toEqual(`<div><kbd>[</kbd> or <kbd>k</kbd></div>`);
});
});
describe('With browser env isMac: true', () => {
beforeEach(() => {
window.gl = { client: { isMac: true } };
});
it.each([
['mod', '<kbd>⌘</kbd>'],
['command', '<kbd>⌘</kbd>'],
['meta', '<kbd>⌘</kbd>'],
['option', '<kbd>⌥</kbd>'],
['alt', '<kbd>⌥</kbd>'],
])('renders platform specific key %p as: %p', (key, rendered) => {
expect(render([key])).toEqual(`<div>${rendered}</div>`);
});
it('does render Mac specific shortcuts', () => {
expect(render(['command+[', 'ctrl+k'])).toEqual(
`<div><kbd>⌘</kbd> + <kbd>[</kbd> or <br><kbd>Ctrl</kbd> + <kbd>k</kbd></div>`,
);
});
});
describe('With browser env isMac: false', () => {
beforeEach(() => {
window.gl = { client: { isMac: false } };
});
it.each([
['mod', '<kbd>Ctrl</kbd>'],
['command', ''],
['meta', ''],
['option', '<kbd>Alt</kbd>'],
['alt', '<kbd>Alt</kbd>'],
])('renders platform specific key %p as: %p', (key, rendered) => {
expect(render([key])).toEqual(`<div>${rendered}</div>`);
});
it('does not render Mac specific shortcuts', () => {
expect(render(['command+[', 'ctrl+k'])).toEqual(`<div><kbd>Ctrl</kbd> + <kbd>k</kbd></div>`);
});
});
});

View File

@ -8,29 +8,50 @@ RSpec.describe Resolvers::RepositoryBranchNamesResolver do
let(:project) { create(:project, :repository) }
describe '#resolve' do
subject(:resolve_branch_names) do
resolve(
described_class,
obj: project.repository,
args: { search_pattern: pattern },
ctx: { current_user: project.creator }
)
end
context 'with empty search pattern' do
let(:pattern) { '' }
it 'returns nil' do
expect(resolve_branch_names).to eq(nil)
expect(resolve_branch_names(pattern, 0, 100)).to eq(nil)
end
end
context 'with a valid search pattern' do
let(:pattern) { 'mas*' }
let(:pattern) { 'snippet/*' }
it 'returns matching branches' do
expect(resolve_branch_names).to match_array(['master'])
expect(resolve_branch_names(pattern, 0, 100)).to contain_exactly(
'snippet/edit-file',
'snippet/multiple-files',
'snippet/no-files',
'snippet/rename-and-edit-file',
'snippet/single-file'
)
end
it 'properly offsets and limits branch name results' do
starting_names = resolve_branch_names(pattern, 0, 3)
offset_names = resolve_branch_names(pattern, 3, 2)
expect(starting_names.count).to eq(3)
expect(offset_names.count).to eq(2)
expect(offset_names).not_to include(*starting_names)
all_names = resolve_branch_names(pattern, 0, 100)
expect(all_names).to contain_exactly(*starting_names, *offset_names)
end
end
end
private
def resolve_branch_names(pattern, offset, limit)
resolve(
described_class,
obj: project.repository,
args: { search_pattern: pattern, offset: offset, limit: limit },
ctx: { current_user: project.creator }
)
end
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountBoardsMetric do
let_it_be(:board) { create(:board) }
it_behaves_like 'a correct instrumented metric value', { time_frame: 'all', data_source: 'database' }, 1
end

View File

@ -20,6 +20,10 @@ RSpec.describe Gitlab::UsageDataMetrics do
it 'includes top level keys' do
expect(subject).to include(:uuid)
end
it 'includes counts keys' do
expect(subject[:counts]).to include(:boards)
end
end
end
end

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Sidebars::Projects::Menus::ProjectOverviewMenu do
RSpec.describe Sidebars::Projects::Menus::ProjectInformationMenu do
let_it_be(:project) { create(:project, :repository) }
let(:user) { project.owner }

View File

@ -1449,6 +1449,48 @@ RSpec.describe API::Users do
end
end
describe "PUT /user/:id/credit_card_validation" do
let(:credit_card_validated_time) { Time.utc(2020, 1, 1) }
context 'when unauthenticated' do
it 'returns authentication error' do
put api("/user/#{user.id}/credit_card_validation"), params: { credit_card_validated_at: credit_card_validated_time }
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
context 'when authenticated as non-admin' do
it "does not allow updating user's credit card validation", :aggregate_failures do
put api("/user/#{user.id}/credit_card_validation", user), params: { credit_card_validated_at: credit_card_validated_time }
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'when authenticated as admin' do
it "updates user's credit card validation", :aggregate_failures do
put api("/user/#{user.id}/credit_card_validation", admin), params: { credit_card_validated_at: credit_card_validated_time }
expect(response).to have_gitlab_http_status(:ok)
expect(user.reload.credit_card_validated_at).to eq(credit_card_validated_time)
end
it "returns 400 error if credit_card_validated_at is missing" do
put api("/user/#{user.id}/credit_card_validation", admin), params: {}
expect(response).to have_gitlab_http_status(:bad_request)
end
it 'returns 404 error if user not found' do
put api("/user/#{non_existing_record_id}/credit_card_validation", admin), params: { credit_card_validated_at: credit_card_validated_time }
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 User Not Found')
end
end
end
describe "DELETE /users/:id/identities/:provider" do
let(:test_user) { create(:omniauth_user, provider: 'ldapmain') }

View File

@ -0,0 +1,84 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Users::UpsertCreditCardValidationService do
let_it_be(:user) { create(:user) }
let(:user_id) { user.id }
let(:credit_card_validated_time) { Time.utc(2020, 1, 1) }
let(:params) { { user_id: user_id, credit_card_validated_at: credit_card_validated_time } }
describe '#execute' do
subject(:service) { described_class.new(params) }
context 'successfully set credit card validation record for the user' do
context 'when user does not have credit card validation record' do
it 'creates the credit card validation and returns a success' do
expect(user.credit_card_validated_at).to be nil
result = service.execute
expect(result.status).to eq(:success)
expect(user.reload.credit_card_validated_at).to eq(credit_card_validated_time)
end
end
context 'when user has credit card validation record' do
let(:old_time) { Time.utc(1999, 2, 2) }
before do
create(:credit_card_validation, user: user, credit_card_validated_at: old_time)
end
it 'updates the credit card validation and returns a success' do
expect(user.credit_card_validated_at).to eq(old_time)
result = service.execute
expect(result.status).to eq(:success)
expect(user.reload.credit_card_validated_at).to eq(credit_card_validated_time)
end
end
end
shared_examples 'returns an error without tracking the exception' do
it do
expect(Gitlab::ErrorTracking).not_to receive(:track_exception)
result = service.execute
expect(result.status).to eq(:error)
end
end
context 'when user id does not exist' do
let(:user_id) { non_existing_record_id }
it_behaves_like 'returns an error without tracking the exception'
end
context 'when missing credit_card_validated_at' do
let(:params) { { user_id: user_id } }
it_behaves_like 'returns an error without tracking the exception'
end
context 'when missing user id' do
let(:params) { { credit_card_validated_at: credit_card_validated_time } }
it_behaves_like 'returns an error without tracking the exception'
end
context 'when unexpected exception happen' do
it 'tracks the exception and returns an error' do
expect(::Users::CreditCardValidation).to receive(:upsert).and_raise(e = StandardError.new('My exception!'))
expect(Gitlab::ErrorTracking).to receive(:track_exception).with(e, class: described_class.to_s, params: params)
result = service.execute
expect(result.status).to eq(:error)
end
end
end
end

View File

@ -41,16 +41,20 @@ RSpec.shared_context 'project navbar structure' do
]
end
let(:project_information_nav_item) do
{
nav_item: _('Project overview'),
nav_sub_items: [
_('Details'),
_('Activity'),
_('Releases')
]
}
end
let(:structure) do
[
{
nav_item: _('Project overview'),
nav_sub_items: [
_('Details'),
_('Activity'),
_('Releases')
]
},
project_information_nav_item,
{
nav_item: _('Repository'),
nav_sub_items: [

View File

@ -19,20 +19,41 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
it_behaves_like 'has nav sidebar'
describe 'Project Overview' do
describe 'Project information' do
it 'has a link to the project path' do
render
expect(rendered).to have_link('Project overview', href: project_path(project), class: %w(shortcuts-project rspec-project-link))
expect(rendered).to have_selector('[aria-label="Project overview"]')
expect(rendered).to have_link('Project information', href: project_path(project), class: %w(shortcuts-project rspec-project-link))
expect(rendered).to have_selector('[aria-label="Project information"]')
end
context 'when feature flag :sidebar_refactor is disabled' do
it 'has a link to the project path' do
stub_feature_flags(sidebar_refactor: false)
render
expect(rendered).to have_link('Project overview', href: project_path(project), class: %w(shortcuts-project rspec-project-link))
expect(rendered).to have_selector('[aria-label="Project overview"]')
end
end
describe 'Details' do
it 'has a link to the projects path' do
it 'does not have a link to the details menu' do
render
expect(rendered).to have_link('Details', href: project_path(project), class: 'shortcuts-project')
expect(rendered).to have_selector('[aria-label="Project details"]')
expect(rendered).not_to have_link('Details', href: project_path(project))
end
context 'when feature flag :sidebar_refactor is disabled' do
it 'has a link to the projects path' do
stub_feature_flags(sidebar_refactor: false)
render
expect(rendered).to have_link('Details', href: project_path(project), class: 'shortcuts-project')
expect(rendered).to have_selector('[aria-label="Project details"]')
end
end
end