Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/issue_templates/Feature Flag Roll Out.md128
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/keybindings.js2
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcut.vue80
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_help.vue574
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.vue6
-rw-r--r--app/assets/javascripts/editor/extensions/editor_lite_extension_base.js2
-rw-r--r--app/assets/stylesheets/bootstrap_migration.scss4
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss9
-rw-r--r--app/assets/stylesheets/framework/kbd.scss15
-rw-r--r--app/assets/stylesheets/framework/typography.scss16
-rw-r--r--app/assets/stylesheets/pages/help.scss44
-rw-r--r--app/finders/repositories/branch_names_finder.rb12
-rw-r--r--app/graphql/resolvers/repository_branch_names_resolver.rb12
-rw-r--r--app/models/concerns/optimized_issuable_label_filter.rb7
-rw-r--r--app/services/users/upsert_credit_card_validation_service.rb20
-rw-r--r--changelogs/unreleased/321364-update-post-eoa-subscriptions.yml5
-rw-r--r--changelogs/unreleased/329141-add-api-set-user-credit-card-validation.yml5
-rw-r--r--changelogs/unreleased/fix-incorrect-issuable-counts.yml5
-rw-r--r--changelogs/unreleased/leipert-dynamic-kbd-help.yml5
-rw-r--r--changelogs/unreleased/mc-feature-add-limit-offset-branch-names-graphql.yml5
-rw-r--r--config/metrics/counts_all/20210216181252_boards.yml11
-rw-r--r--db/post_migrate/20210303121224_update_gitlab_subscriptions_start_at_post_eoa.rb54
-rw-r--r--db/schema_migrations/202103031212241
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md2
-rw-r--r--doc/api/graphql/reference/index.md2
-rw-r--r--doc/ci/pipelines/job_artifacts.md2
-rw-r--r--doc/development/usage_ping/dictionary.md2
-rw-r--r--doc/user/analytics/index.md42
-rw-r--r--doc/user/analytics/merge_request_analytics.md6
-rw-r--r--doc/user/packages/index.md27
-rw-r--r--lib/api/entities/user_credit_card_validations.rb9
-rw-r--r--lib/api/users.rb24
-rw-r--r--lib/gitlab/usage/metrics/instrumentations/count_boards_metric.rb15
-rw-r--r--lib/sidebars/projects/menus/project_information_menu.rb (renamed from lib/sidebars/projects/menus/project_overview_menu.rb)23
-rw-r--r--lib/sidebars/projects/panel.rb2
-rw-r--r--locale/gitlab.pot58
-rw-r--r--qa/qa/page/project/show.rb2
-rw-r--r--qa/qa/page/project/web_ide/edit.rb24
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/link_to_line_in_web_ide_spec.rb43
-rw-r--r--spec/features/groups_spec.rb4
-rw-r--r--spec/features/projects/active_tabs_spec.rb27
-rw-r--r--spec/features/projects/navbar_spec.rb10
-rw-r--r--spec/features/projects/user_uses_shortcuts_spec.rb17
-rw-r--r--spec/finders/merge_requests_finder_spec.rb31
-rw-r--r--spec/finders/repositories/branch_names_finder_spec.rb39
-rw-r--r--spec/frontend/behaviors/shortcuts/shortcut_spec.js96
-rw-r--r--spec/graphql/resolvers/repository_branch_names_resolver_spec.rb45
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/count_boards_metric_spec.rb9
-rw-r--r--spec/lib/gitlab/usage_data_metrics_spec.rb4
-rw-r--r--spec/lib/sidebars/projects/menus/project_information_menu_spec.rb (renamed from spec/lib/sidebars/projects/menus/project_overview_menu_spec.rb)2
-rw-r--r--spec/requests/api/users_spec.rb42
-rw-r--r--spec/services/users/upsert_credit_card_validation_service_spec.rb84
-rw-r--r--spec/support/shared_contexts/navbar_structure_context.rb20
-rw-r--r--spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb33
57 files changed, 1053 insertions, 722 deletions
diff --git a/.gitlab/issue_templates/Feature Flag Roll Out.md b/.gitlab/issue_templates/Feature Flag Roll Out.md
index 39f4de94553..449569df554 100644
--- a/.gitlab/issue_templates/Feature Flag Roll Out.md
+++ b/.gitlab/issue_templates/Feature Flag Roll Out.md
@@ -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
-
-<!-- Please check which steps are needed and remove those which don't apply -->
-
-**Rollout Steps**
-
-*Preparation Phase*
-- [ ] Enable on staging (`/chatops run feature set feature_name true --staging`)
+## Rollout Steps
-- [ ] Test on staging
+### Rollout on non-production environments
-- [ ] Ensure that documentation has been updated ([More info](https://docs.gitlab.com/ee/development/documentation/feature_flags.html#features-that-became-enabled-by-default))
+- [ ] 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.
-- [ ] 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.
-
-- [ ] 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.
-
-*Partial Rollout Phase*
-
-- [ ] Enable on GitLab.com for individual groups/projects listed above and verify behaviour (`/chatops run feature set --project=gitlab-org/gitlab feature_name true`)
-
-- [ ] Verify behaviour (See Beta Groups) and add details with screenshots as a comment on this issue
-
-- [ ] 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`
-
-*Full Rollout Phase*
-- [ ] Make the feature flag enabled by default i.e. Change `default_enabled` to `true`
-
-- [ ] 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
-
-- [ ] Announce on the issue that the flag has been enabled
-
-- [ ] Create a cleanup issue using the "Feature Flag Removal" template
+ [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.
+
+### Global rollout on production
+
+- [ ] [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).
+
+### (Optional) Release the feature with the feature flag
+
+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:
+
+- [ ] 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.
+
+**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.
+
+### Release the feature
+
+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.
+
+<!-- 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"
diff --git a/Gemfile b/Gemfile
index 1e7a6bf8306..b228e42aeea 100644
--- a/Gemfile
+++ b/Gemfile
@@ -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'
diff --git a/Gemfile.lock b/Gemfile.lock
index 70d4e89976a..a703d29bc4e 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -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)
diff --git a/app/assets/javascripts/behaviors/shortcuts/keybindings.js b/app/assets/javascripts/behaviors/shortcuts/keybindings.js
index 6abbd7f3243..c63dba05f10 100644
--- a/app/assets/javascripts/behaviors/shortcuts/keybindings.js
+++ b/app/assets/javascripts/behaviors/shortcuts/keybindings.js
@@ -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,
};
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcut.vue b/app/assets/javascripts/behaviors/shortcuts/shortcut.vue
new file mode 100644
index 00000000000..e5992779a99
--- /dev/null
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcut.vue
@@ -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>
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_help.vue b/app/assets/javascripts/behaviors/shortcuts/shortcuts_help.vue
index 49216cc4aa0..cb7c6f9f6bc 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_help.vue
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_help.vue
@@ -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>
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.vue b/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.vue
index 6cbe443062a..8f1518a1c9c 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.vue
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_toggle.vue
@@ -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>
diff --git a/app/assets/javascripts/editor/extensions/editor_lite_extension_base.js b/app/assets/javascripts/editor/extensions/editor_lite_extension_base.js
index 3d4f08131c1..447d8e9b8c6 100644
--- a/app/assets/javascripts/editor/extensions/editor_lite_extension_base.js
+++ b/app/assets/javascripts/editor/extensions/editor_lite_extension_base.js
@@ -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) => {
diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss
index a5cfc8d12b0..c4f292dd05d 100644
--- a/app/assets/stylesheets/bootstrap_migration.scss
+++ b/app/assets/stylesheets/bootstrap_migration.scss
@@ -105,10 +105,6 @@ hr {
}
}
-kbd {
- display: inline-block;
-}
-
code {
padding: 2px 4px;
color: $code-color;
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 87e4bb50984..cde5ad24fa5 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -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';
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index aedb54c6d11..a3e3cbd3e38 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -266,15 +266,6 @@
}
}
- .shortcut-mappings {
- display: none;
- }
-
- &.shortcuts .shortcut-mappings {
- display: inline-block;
- margin-right: 5px;
- }
-
ul {
margin: 0;
padding: 0;
diff --git a/app/assets/stylesheets/framework/kbd.scss b/app/assets/stylesheets/framework/kbd.scss
new file mode 100644
index 00000000000..05991bc16fd
--- /dev/null
+++ b/app/assets/stylesheets/framework/kbd.scss
@@ -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;
+}
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 648ae29e212..603b05efe10 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -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;
diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss
index c05216ac6e6..9182292ffd3 100644
--- a/app/assets/stylesheets/pages/help.scss
+++ b/app/assets/stylesheets/pages/help.scss
@@ -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;
+
+ &-title {
+ margin-left: 40%;
+ }
- .shortcut {
- padding-right: 10px;
- color: $gray-300;
- text-align: right;
- white-space: nowrap;
+ kbd {
+ margin: 0.1rem 0;
+ line-height: unset;
+ font-size: unset;
+ }
}
}
diff --git a/app/finders/repositories/branch_names_finder.rb b/app/finders/repositories/branch_names_finder.rb
index 5bb67425aa5..8c8c7405407 100644
--- a/app/finders/repositories/branch_names_finder.rb
+++ b/app/finders/repositories/branch_names_finder.rb
@@ -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
diff --git a/app/graphql/resolvers/repository_branch_names_resolver.rb b/app/graphql/resolvers/repository_branch_names_resolver.rb
index 45cfe229b2f..c0a5ea0366f 100644
--- a/app/graphql/resolvers/repository_branch_names_resolver.rb
+++ b/app/graphql/resolvers/repository_branch_names_resolver.rb
@@ -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
diff --git a/app/models/concerns/optimized_issuable_label_filter.rb b/app/models/concerns/optimized_issuable_label_filter.rb
index c7af841e450..19d2ac620f3 100644
--- a/app/models/concerns/optimized_issuable_label_filter.rb
+++ b/app/models/concerns/optimized_issuable_label_filter.rb
@@ -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
diff --git a/app/services/users/upsert_credit_card_validation_service.rb b/app/services/users/upsert_credit_card_validation_service.rb
new file mode 100644
index 00000000000..70a96b3ec6b
--- /dev/null
+++ b/app/services/users/upsert_credit_card_validation_service.rb
@@ -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
diff --git a/changelogs/unreleased/321364-update-post-eoa-subscriptions.yml b/changelogs/unreleased/321364-update-post-eoa-subscriptions.yml
new file mode 100644
index 00000000000..0741345174e
--- /dev/null
+++ b/changelogs/unreleased/321364-update-post-eoa-subscriptions.yml
@@ -0,0 +1,5 @@
+---
+title: Add migration to update plans on new post-EoA subscriptions
+merge_request: 55625
+author:
+type: changed
diff --git a/changelogs/unreleased/329141-add-api-set-user-credit-card-validation.yml b/changelogs/unreleased/329141-add-api-set-user-credit-card-validation.yml
new file mode 100644
index 00000000000..92cc00e92e8
--- /dev/null
+++ b/changelogs/unreleased/329141-add-api-set-user-credit-card-validation.yml
@@ -0,0 +1,5 @@
+---
+title: Add API to set credit card validation timestamp for user
+merge_request: 60828
+author:
+type: added
diff --git a/changelogs/unreleased/fix-incorrect-issuable-counts.yml b/changelogs/unreleased/fix-incorrect-issuable-counts.yml
new file mode 100644
index 00000000000..412b291b2b3
--- /dev/null
+++ b/changelogs/unreleased/fix-incorrect-issuable-counts.yml
@@ -0,0 +1,5 @@
+---
+title: Fix incorrect issue and merge requests counts with filters
+merge_request: 61230
+author:
+type: fixed
diff --git a/changelogs/unreleased/leipert-dynamic-kbd-help.yml b/changelogs/unreleased/leipert-dynamic-kbd-help.yml
new file mode 100644
index 00000000000..0b7fe113820
--- /dev/null
+++ b/changelogs/unreleased/leipert-dynamic-kbd-help.yml
@@ -0,0 +1,5 @@
+---
+title: "Update Keyboard shortcut help: adding search, update styling"
+merge_request: 56400
+author:
+type: changed
diff --git a/changelogs/unreleased/mc-feature-add-limit-offset-branch-names-graphql.yml b/changelogs/unreleased/mc-feature-add-limit-offset-branch-names-graphql.yml
new file mode 100644
index 00000000000..0426430a41f
--- /dev/null
+++ b/changelogs/unreleased/mc-feature-add-limit-offset-branch-names-graphql.yml
@@ -0,0 +1,5 @@
+---
+title: Add offset and limit to branch names resolver.
+merge_request: 61061
+author:
+type: changed
diff --git a/config/metrics/counts_all/20210216181252_boards.yml b/config/metrics/counts_all/20210216181252_boards.yml
index 7552c7289c7..45844a54aa8 100644
--- a/config/metrics/counts_all/20210216181252_boards.yml
+++ b/config/metrics/counts_all/20210216181252_boards.yml
@@ -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
diff --git a/db/post_migrate/20210303121224_update_gitlab_subscriptions_start_at_post_eoa.rb b/db/post_migrate/20210303121224_update_gitlab_subscriptions_start_at_post_eoa.rb
new file mode 100644
index 00000000000..69d99704469
--- /dev/null
+++ b/db/post_migrate/20210303121224_update_gitlab_subscriptions_start_at_post_eoa.rb
@@ -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
diff --git a/db/schema_migrations/20210303121224 b/db/schema_migrations/20210303121224
new file mode 100644
index 00000000000..0c0ba7c882c
--- /dev/null
+++ b/db/schema_migrations/20210303121224
@@ -0,0 +1 @@
+cef2421a6885cb8b28d34388af6c79c4be1564dfd5fae2efcb35622d511eb8c0 \ No newline at end of file
diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md
index e8cf090b19e..45dabc387bd 100644
--- a/doc/administration/monitoring/prometheus/gitlab_metrics.md
+++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md
@@ -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 | |
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index d9d6c6d5fe7..565f2da07eb 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -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`
diff --git a/doc/ci/pipelines/job_artifacts.md b/doc/ci/pipelines/job_artifacts.md
index fa5da1f6a11..76f05f5e1e7 100644
--- a/doc/ci/pipelines/job_artifacts.md
+++ b/doc/ci/pipelines/job_artifacts.md
@@ -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:
diff --git a/doc/development/usage_ping/dictionary.md b/doc/development/usage_ping/dictionary.md
index 0ec481511fa..8cee670f72a 100644
--- a/doc/development/usage_ping/dictionary.md
+++ b/doc/development/usage_ping/dictionary.md
@@ -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)
diff --git a/doc/user/analytics/index.md b/doc/user/analytics/index.md
index 5ab5fe9300a..2f83ddd7a0e 100644
--- a/doc/user/analytics/index.md
+++ b/doc/user/analytics/index.md
@@ -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)
diff --git a/doc/user/analytics/merge_request_analytics.md b/doc/user/analytics/merge_request_analytics.md
index 909eb7e585f..321e2f0fc24 100644
--- a/doc/user/analytics/merge_request_analytics.md
+++ b/doc/user/analytics/merge_request_analytics.md
@@ -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,
diff --git a/doc/user/packages/index.md b/doc/user/packages/index.md
index ca6df5e0f84..b871a08c133 100644
--- a/doc/user/packages/index.md
+++ b/doc/user/packages/index.md
@@ -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.
diff --git a/lib/api/entities/user_credit_card_validations.rb b/lib/api/entities/user_credit_card_validations.rb
new file mode 100644
index 00000000000..fcd42388b16
--- /dev/null
+++ b/lib/api/entities/user_credit_card_validations.rb
@@ -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
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 078ba7542a3..565a3544da2 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -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.'
diff --git a/lib/gitlab/usage/metrics/instrumentations/count_boards_metric.rb b/lib/gitlab/usage/metrics/instrumentations/count_boards_metric.rb
new file mode 100644
index 00000000000..4e1ba027bca
--- /dev/null
+++ b/lib/gitlab/usage/metrics/instrumentations/count_boards_metric.rb
@@ -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
diff --git a/lib/sidebars/projects/menus/project_overview_menu.rb b/lib/sidebars/projects/menus/project_information_menu.rb
index 32ea63e4917..3d6362cf187 100644
--- a/lib/sidebars/projects/menus/project_overview_menu.rb
+++ b/lib/sidebars/projects/menus/project_information_menu.rb
@@ -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),
diff --git a/lib/sidebars/projects/panel.rb b/lib/sidebars/projects/panel.rb
index 3be2a2b2b71..dbf2977df01 100644
--- a/lib/sidebars/projects/panel.rb
+++ b/lib/sidebars/projects/panel.rb
@@ -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))
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index c6fa42e17eb..93235d47b2d 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -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,13 +33801,13 @@ msgstr ""
msgid "Toggle project select"
msgstr ""
-msgid "Toggle sidebar"
+msgid "Toggle shortcuts"
msgstr ""
-msgid "Toggle the Performance Bar"
+msgid "Toggle sidebar"
msgstr ""
-msgid "Toggle this dialog"
+msgid "Toggle the Performance Bar"
msgstr ""
msgid "Toggle thread"
@@ -38934,6 +38973,9 @@ msgstr ""
msgid "the wiki"
msgstr ""
+msgid "then"
+msgstr ""
+
msgid "this document"
msgstr ""
diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb
index 6cc99b6cfc1..7453326573f 100644
--- a/qa/qa/page/project/show.rb
+++ b/qa/qa/page/project/show.rb
@@ -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?
diff --git a/qa/qa/page/project/web_ide/edit.rb b/qa/qa/page/project/web_ide/edit.rb
index e8e83a10a7f..c6cd572a680 100644
--- a/qa/qa/page/project/web_ide/edit.rb
+++ b/qa/qa/page/project/web_ide/edit.rb
@@ -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
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/link_to_line_in_web_ide_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/link_to_line_in_web_ide_spec.rb
new file mode 100644
index 00000000000..c7fc01303b7
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/link_to_line_in_web_ide_spec.rb
@@ -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
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index 0fab5718aa6..bcccadf7710 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -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
diff --git a/spec/features/projects/active_tabs_spec.rb b/spec/features/projects/active_tabs_spec.rb
index 9de43e7d18c..68695840808 100644
--- a/spec/features/projects/active_tabs_spec.rb
+++ b/spec/features/projects/active_tabs_spec.rb
@@ -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
diff --git a/spec/features/projects/navbar_spec.rb b/spec/features/projects/navbar_spec.rb
index d01e4aa85ab..662bbd9b26f 100644
--- a/spec/features/projects/navbar_spec.rb
+++ b/spec/features/projects/navbar_spec.rb
@@ -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 })
diff --git a/spec/features/projects/user_uses_shortcuts_spec.rb b/spec/features/projects/user_uses_shortcuts_spec.rb
index b73a1f6b9fa..cf97897d976 100644
--- a/spec/features/projects/user_uses_shortcuts_spec.rb
+++ b/spec/features/projects/user_uses_shortcuts_spec.rb
@@ -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
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index 597d22801ca..3b835d366db 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -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
diff --git a/spec/finders/repositories/branch_names_finder_spec.rb b/spec/finders/repositories/branch_names_finder_spec.rb
index 4d8bfcc0f20..40f5d339832 100644
--- a/spec/finders/repositories/branch_names_finder_spec.rb
+++ b/spec/finders/repositories/branch_names_finder_spec.rb
@@ -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
diff --git a/spec/frontend/behaviors/shortcuts/shortcut_spec.js b/spec/frontend/behaviors/shortcuts/shortcut_spec.js
new file mode 100644
index 00000000000..44bb74ce179
--- /dev/null
+++ b/spec/frontend/behaviors/shortcuts/shortcut_spec.js
@@ -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>`);
+ });
+ });
+});
diff --git a/spec/graphql/resolvers/repository_branch_names_resolver_spec.rb b/spec/graphql/resolvers/repository_branch_names_resolver_spec.rb
index 398dd7a2e2e..004e0411e51 100644
--- a/spec/graphql/resolvers/repository_branch_names_resolver_spec.rb
+++ b/spec/graphql/resolvers/repository_branch_names_resolver_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_boards_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_boards_metric_spec.rb
new file mode 100644
index 00000000000..52c1ccdcd47
--- /dev/null
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_boards_metric_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/usage_data_metrics_spec.rb b/spec/lib/gitlab/usage_data_metrics_spec.rb
index 1cb43c2886c..e841f680a32 100644
--- a/spec/lib/gitlab/usage_data_metrics_spec.rb
+++ b/spec/lib/gitlab/usage_data_metrics_spec.rb
@@ -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
diff --git a/spec/lib/sidebars/projects/menus/project_overview_menu_spec.rb b/spec/lib/sidebars/projects/menus/project_information_menu_spec.rb
index 91682a9e415..ddf9e779219 100644
--- a/spec/lib/sidebars/projects/menus/project_overview_menu_spec.rb
+++ b/spec/lib/sidebars/projects/menus/project_information_menu_spec.rb
@@ -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 }
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 01a24be9f20..71fdd986f20 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -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') }
diff --git a/spec/services/users/upsert_credit_card_validation_service_spec.rb b/spec/services/users/upsert_credit_card_validation_service_spec.rb
new file mode 100644
index 00000000000..148638fe5e7
--- /dev/null
+++ b/spec/services/users/upsert_credit_card_validation_service_spec.rb
@@ -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
diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb
index 37f18e52449..52c8f2b5986 100644
--- a/spec/support/shared_contexts/navbar_structure_context.rb
+++ b/spec/support/shared_contexts/navbar_structure_context.rb
@@ -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: [
diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
index 9087b694d63..11bb81bcd40 100644
--- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
@@ -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