Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
2cf4bdd0b0
commit
6352d2f7c2
|
|
@ -1,6 +1,6 @@
|
||||||
include:
|
include:
|
||||||
- project: gitlab-org/quality/pipeline-common
|
- project: gitlab-org/quality/pipeline-common
|
||||||
ref: 7.13.3
|
ref: 8.0.0
|
||||||
file:
|
file:
|
||||||
- /ci/danger-review.yml
|
- /ci/danger-review.yml
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
b8fda37aff7f658c1b0195fd36cefc93fa3bb718
|
fd52f0c5d44c30584f934ea7774d4024654b63a3
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlSearchBoxByClick, GlButton } from '@gitlab/ui';
|
import { GlSearchBoxByType, GlButton } from '@gitlab/ui';
|
||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import { mapState, mapActions } from 'vuex';
|
import { mapState, mapActions } from 'vuex';
|
||||||
import { s__ } from '~/locale';
|
import { s__ } from '~/locale';
|
||||||
|
|
@ -22,7 +22,7 @@ export default {
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
GlButton,
|
GlButton,
|
||||||
GlSearchBoxByClick,
|
GlSearchBoxByType,
|
||||||
GroupFilter,
|
GroupFilter,
|
||||||
ProjectFilter,
|
ProjectFilter,
|
||||||
MarkdownDrawer,
|
MarkdownDrawer,
|
||||||
|
|
@ -87,18 +87,9 @@ export default {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section>
|
<section>
|
||||||
<div class="gl-display-flex gl-flex-wrap gl-justify-content-end gl-pt-6 gl-pb-5">
|
<div class="gl-lg-display-flex gl-flex-direction-row gl-justify-content-space-between gl-pt-5">
|
||||||
<search-type-indicator />
|
|
||||||
</div>
|
|
||||||
<div class="gl-p-5 gl-bg-gray-10 gl-border-b gl-border-t">
|
|
||||||
<div class="search-page-form gl-lg-display-flex gl-flex-direction-column">
|
|
||||||
<div class="gl-lg-display-flex gl-flex-direction-row gl-align-items-flex-end">
|
|
||||||
<div class="gl-flex-grow-1 gl-mb-4 gl-lg-mb-0 gl-lg-mr-2">
|
|
||||||
<div
|
|
||||||
class="gl-display-flex gl-flex-direction-row gl-justify-content-space-between gl-mb-0 gl-md-mb-4"
|
|
||||||
>
|
|
||||||
<label class="gl-mb-1 gl-md-pb-2">{{ $options.i18n.searchLabel }}</label>
|
|
||||||
<template v-if="showSyntaxOptions">
|
<template v-if="showSyntaxOptions">
|
||||||
|
<div class="gl-pb-6">
|
||||||
<gl-button
|
<gl-button
|
||||||
category="tertiary"
|
category="tertiary"
|
||||||
variant="link"
|
variant="link"
|
||||||
|
|
@ -107,15 +98,20 @@ export default {
|
||||||
@click="onToggleDrawer"
|
@click="onToggleDrawer"
|
||||||
>{{ $options.i18n.syntaxOptionsLabel }}
|
>{{ $options.i18n.syntaxOptionsLabel }}
|
||||||
</gl-button>
|
</gl-button>
|
||||||
|
</div>
|
||||||
<markdown-drawer ref="markdownDrawer" :document-path="documentBasedOnSearchType" />
|
<markdown-drawer ref="markdownDrawer" :document-path="documentBasedOnSearchType" />
|
||||||
</template>
|
</template>
|
||||||
|
<search-type-indicator />
|
||||||
</div>
|
</div>
|
||||||
<gl-search-box-by-click
|
<div class="search-page-form gl-lg-display-flex gl-flex-direction-row gl-align-items-flex-end">
|
||||||
|
<div class="gl-flex-grow-1 gl-lg-mb-0 gl-lg-mr-2">
|
||||||
|
<gl-search-box-by-type
|
||||||
id="dashboard_search"
|
id="dashboard_search"
|
||||||
v-model="search"
|
v-model="search"
|
||||||
name="search"
|
name="search"
|
||||||
:placeholder="$options.i18n.searchPlaceholder"
|
:placeholder="$options.i18n.searchPlaceholder"
|
||||||
@submit="applyQuery"
|
@submit="applyQuery"
|
||||||
|
@keydown.enter.stop.prevent="applyQuery"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-mx-3 gl-min-w-20">
|
<div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-mx-3 gl-min-w-20">
|
||||||
|
|
@ -134,7 +130,5 @@ export default {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,6 @@ export default {
|
||||||
searchInProgress: false,
|
searchInProgress: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {},
|
|
||||||
watch: {
|
watch: {
|
||||||
items() {
|
items() {
|
||||||
if (this.searchText === '') {
|
if (this.searchText === '') {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class RemoveUseLegacyWebIdeColumn < Gitlab::Database::Migration[2.2]
|
||||||
|
milestone '16.7'
|
||||||
|
enable_lock_retries!
|
||||||
|
|
||||||
|
def up
|
||||||
|
remove_column :user_preferences, :use_legacy_web_ide
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
add_column :user_preferences, :use_legacy_web_ide, :boolean, default: false, null: false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
f425d63a9d6a474162cb59cf0aea0d0d56cd38e30cb32fde8387ca9e247187c9
|
||||||
|
|
@ -24653,7 +24653,6 @@ CREATE TABLE user_preferences (
|
||||||
diffs_deletion_color text,
|
diffs_deletion_color text,
|
||||||
diffs_addition_color text,
|
diffs_addition_color text,
|
||||||
markdown_automatic_lists boolean DEFAULT true NOT NULL,
|
markdown_automatic_lists boolean DEFAULT true NOT NULL,
|
||||||
use_legacy_web_ide boolean DEFAULT false NOT NULL,
|
|
||||||
use_new_navigation boolean,
|
use_new_navigation boolean,
|
||||||
achievements_enabled boolean DEFAULT true NOT NULL,
|
achievements_enabled boolean DEFAULT true NOT NULL,
|
||||||
pinned_nav_items jsonb DEFAULT '{}'::jsonb NOT NULL,
|
pinned_nav_items jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||||
|
|
|
||||||
|
|
@ -198,7 +198,7 @@ http://secondary.example.com/
|
||||||
Last status report was: 1 minute ago
|
Last status report was: 1 minute ago
|
||||||
```
|
```
|
||||||
|
|
||||||
There are up to three statuses for each item. For example, for `Project Repositories`, you see the following lines:
|
Each item can have up to three statuses. For example, for `Project Repositories`, you see the following lines:
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
Project Repositories: succeeded 12345 / total 12345 (100%)
|
Project Repositories: succeeded 12345 / total 12345 (100%)
|
||||||
|
|
@ -738,16 +738,75 @@ If large repositories are affected by this problem,
|
||||||
their resync may take a long time and cause significant load on your Geo sites,
|
their resync may take a long time and cause significant load on your Geo sites,
|
||||||
storage and network systems.
|
storage and network systems.
|
||||||
|
|
||||||
If you see the error message `Synchronization failed - Error syncing repository` along with `fatal: fsck error in packed object`, this indicates
|
The following error message indicates a consistency check error when syncing the repository:
|
||||||
a consistency check error when syncing the repository.
|
|
||||||
|
|
||||||
One example of a consistency error is: `error: object f4a87a3be694fbbd6e50a668a31a8513caeaafe3: hasDotgit: contains '.git`.
|
```plaintext
|
||||||
|
Synchronization failed - Error syncing repository [..] fatal: fsck error in packed object
|
||||||
|
```
|
||||||
|
|
||||||
Removing the malformed objects causing consistency errors require rewriting the repository history, which is not always an option. However,
|
Several issues can trigger this error. For example, problems with email addresses:
|
||||||
it's possible to override the consistency checks instead. To do that, follow
|
|
||||||
[the instructions in the Gitaly docs](../../gitaly/configure_gitaly.md#repository-consistency-checks).
|
|
||||||
|
|
||||||
You can also get the error message `Synchronization failed - Error syncing repository` along with the following log messages, this indicates that the expected `geo` remote is not present in the `.git/config` file
|
```plaintext
|
||||||
|
Error syncing repository: 13:fetch remote: "error: object <SHA>: badEmail: invalid author/committer line - bad email
|
||||||
|
fatal: fsck error in packed object
|
||||||
|
fatal: fetch-pack: invalid index-pack output
|
||||||
|
```
|
||||||
|
|
||||||
|
Another issue that can trigger this error is `object <SHA>: hasDotgit: contains '.git'`. Check the specific errors because you might have more than one problem across all
|
||||||
|
your repositories.
|
||||||
|
|
||||||
|
A second synchronization error can also be caused by repository check issues:
|
||||||
|
|
||||||
|
```plaintext
|
||||||
|
Error syncing repository: 13:Received RST_STREAM with error code 2.
|
||||||
|
```
|
||||||
|
|
||||||
|
These errors can be observed by [immediately syncing all failed repositories](#sync-all-failed-repositories-now).
|
||||||
|
|
||||||
|
Removing the malformed objects causing consistency errors involves rewriting the repository history, which is usually not an option.
|
||||||
|
|
||||||
|
To ignore these consistency checks, reconfigure Gitaly **on the secondary Geo sites** to ignore these `git fsck` issues.
|
||||||
|
The following configuration example:
|
||||||
|
|
||||||
|
- [Uses the new configuration structure](../../../update/versions/gitlab_16_changes.md#gitaly-configuration-structure-change) required from GitLab 16.0.
|
||||||
|
- Ignores five common check failures.
|
||||||
|
|
||||||
|
[The Gitaly documentation has more details](../../gitaly/configure_gitaly.md#repository-consistency-checks)
|
||||||
|
about other Git check failures and older versions of GitLab.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
gitaly['configuration'] = {
|
||||||
|
git: {
|
||||||
|
config: [
|
||||||
|
{ key: "fsck.duplicateEntries", value: "ignore" },
|
||||||
|
{ key: "fsck.badFilemode", value: "ignore" },
|
||||||
|
{ key: "fsck.missingEmail", value: "ignore" },
|
||||||
|
{ key: "fsck.badEmail", value: "ignore" },
|
||||||
|
{ key: "fsck.hasDotgit", value: "ignore" },
|
||||||
|
{ key: "fetch.fsck.duplicateEntries", value: "ignore" },
|
||||||
|
{ key: "fetch.fsck.badFilemode", value: "ignore" },
|
||||||
|
{ key: "fetch.fsck.missingEmail", value: "ignore" },
|
||||||
|
{ key: "fetch.fsck.badEmail", value: "ignore" },
|
||||||
|
{ key: "fetch.fsck.hasDotgit", value: "ignore" },
|
||||||
|
{ key: "receive.fsck.duplicateEntries", value: "ignore" },
|
||||||
|
{ key: "receive.fsck.badFilemode", value: "ignore" },
|
||||||
|
{ key: "receive.fsck.missingEmail", value: "ignore" },
|
||||||
|
{ key: "receive.fsck.badEmail", value: "ignore" },
|
||||||
|
{ key: "receive.fsck.hasDotgit", value: "ignore" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
GitLab 16.1 and later [include an enhancement](https://gitlab.com/gitlab-org/gitaly/-/merge_requests/5879) that might resolve some of these issues.
|
||||||
|
|
||||||
|
[Gitaly issue 5625](https://gitlab.com/gitlab-org/gitaly/-/issues/5625) proposes to ensure that Geo replicates repositories even if the source repository contains
|
||||||
|
problematic commits.
|
||||||
|
|
||||||
|
#### Related error `does not appear to be a git repository`
|
||||||
|
|
||||||
|
You can also get the error message `Synchronization failed - Error syncing repository` along with the following log messages.
|
||||||
|
This error indicates that the expected Geo remote is not present in the `.git/config` file
|
||||||
of a repository on the secondary Geo site's file system:
|
of a repository on the secondary Geo site's file system:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|
@ -965,6 +1024,11 @@ The following script:
|
||||||
- Displays the project details and the reasons for the last failure.
|
- Displays the project details and the reasons for the last failure.
|
||||||
- Attempts to resync the repository.
|
- Attempts to resync the repository.
|
||||||
- Reports back if a failure occurs, and why.
|
- Reports back if a failure occurs, and why.
|
||||||
|
- Might take some time to complete. Each repository check must complete
|
||||||
|
before reporting back the result. If your session times out, take measures
|
||||||
|
to allow the process to continue running such as starting a `screen` session,
|
||||||
|
or running it using [Rails runner](../../operations/rails_console.md#using-the-rails-runner)
|
||||||
|
and `nohup`.
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
Geo::ProjectRegistry.sync_failed('repository').find_each do |p|
|
Geo::ProjectRegistry.sync_failed('repository').find_each do |p|
|
||||||
|
|
|
||||||
|
|
@ -21278,6 +21278,7 @@ Represents a member role.
|
||||||
| <a id="memberroleenabledpermissions"></a>`enabledPermissions` **{warning-solid}** | [`[MemberRolePermission!]`](#memberrolepermission) | **Introduced** in 16.5. This feature is an Experiment. It can be changed or removed at any time. Array of all permissions enabled for the custom role. |
|
| <a id="memberroleenabledpermissions"></a>`enabledPermissions` **{warning-solid}** | [`[MemberRolePermission!]`](#memberrolepermission) | **Introduced** in 16.5. This feature is an Experiment. It can be changed or removed at any time. Array of all permissions enabled for the custom role. |
|
||||||
| <a id="memberroleid"></a>`id` | [`MemberRoleID!`](#memberroleid) | ID of the member role. |
|
| <a id="memberroleid"></a>`id` | [`MemberRoleID!`](#memberroleid) | ID of the member role. |
|
||||||
| <a id="memberrolemanageprojectaccesstokens"></a>`manageProjectAccessTokens` **{warning-solid}** | [`Boolean`](#boolean) | **Introduced** in 16.5. This feature is an Experiment. It can be changed or removed at any time. Permission to admin project access tokens. |
|
| <a id="memberrolemanageprojectaccesstokens"></a>`manageProjectAccessTokens` **{warning-solid}** | [`Boolean`](#boolean) | **Introduced** in 16.5. This feature is an Experiment. It can be changed or removed at any time. Permission to admin project access tokens. |
|
||||||
|
| <a id="memberrolememberscount"></a>`membersCount` **{warning-solid}** | [`Int!`](#int) | **Introduced** in 16.7. This feature is an Experiment. It can be changed or removed at any time. Total number of members with the custom role. |
|
||||||
| <a id="memberrolename"></a>`name` | [`String!`](#string) | Name of the member role. |
|
| <a id="memberrolename"></a>`name` | [`String!`](#string) | Name of the member role. |
|
||||||
| <a id="memberrolereadcode"></a>`readCode` **{warning-solid}** | [`Boolean`](#boolean) | **Introduced** in 16.5. This feature is an Experiment. It can be changed or removed at any time. Permission to read code. |
|
| <a id="memberrolereadcode"></a>`readCode` **{warning-solid}** | [`Boolean`](#boolean) | **Introduced** in 16.5. This feature is an Experiment. It can be changed or removed at any time. Permission to read code. |
|
||||||
| <a id="memberrolereaddependency"></a>`readDependency` **{warning-solid}** | [`Boolean`](#boolean) | **Introduced** in 16.5. This feature is an Experiment. It can be changed or removed at any time. Permission to read dependency. |
|
| <a id="memberrolereaddependency"></a>`readDependency` **{warning-solid}** | [`Boolean`](#boolean) | **Introduced** in 16.5. This feature is an Experiment. It can be changed or removed at any time. Permission to read dependency. |
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
status: proposed
|
status: proposed
|
||||||
creation-date: "2023-10-10"
|
creation-date: "2023-10-10"
|
||||||
authors: [ "@thomasrandolph", "@patrickbajao", "@igor.drozdov", "@jerasmus", "@iamphill" ]
|
authors: [ "@thomasrandolph", "@patrickbajao", "@igor.drozdov", "@jerasmus", "@iamphill", "@slashmanov" ]
|
||||||
coach: [ "@ntepluhina" ]
|
coach: [ "@ntepluhina" ]
|
||||||
approvers: [ ]
|
approvers: [ ]
|
||||||
owning-stage: "~devops::create"
|
owning-stage: "~devops::create"
|
||||||
|
|
@ -55,16 +55,34 @@ In essence, we'll strive to meet every goal at each decision but prioritise the
|
||||||
|
|
||||||
## Proposal
|
## Proposal
|
||||||
|
|
||||||
<!--
|
New diffs introduce a paradigm shift in our approach to rendering diffs.
|
||||||
This is where we get down to the specifics of what the proposal actually is,
|
Previously, we had two different approaches to rendering diffs:
|
||||||
but keep it simple! This should have enough detail that reviewers can
|
|
||||||
understand exactly what you're proposing, but should not include things like
|
|
||||||
API designs or implementation. The "Design Details" section below is for the
|
|
||||||
real nitty-gritty.
|
|
||||||
|
|
||||||
You might want to consider including the pros and cons of the proposed solution so that they can be
|
1. Merge requests heavily utilized client-side rendering.
|
||||||
compared with the pros and cons of alternatives.
|
1. All other pages used server-side rendering with sprinkles of JavaScript.
|
||||||
-->
|
|
||||||
|
In merge requests, most of the rendering work was done on the client:
|
||||||
|
|
||||||
|
- The backend would only generate a JSON response with diffs data.
|
||||||
|
- The client would be responsible for both drawing the diffs and reacting to user input.
|
||||||
|
|
||||||
|
This led to us adopting a
|
||||||
|
[virtualized scrolling solution](https://github.com/Akryum/vue-virtual-scroller/tree/v1/packages/vue-virtual-scroller)
|
||||||
|
for client-side rendering, which sped up drawing large diff file lists significantly.
|
||||||
|
|
||||||
|
Unfortunately, this came with downsides of a very high maintenance cost and
|
||||||
|
[constant bugs](https://gitlab.com/gitlab-org/gitlab/-/issues/427155#note_1607184794).
|
||||||
|
The user experience also suffered because we couldn't show diffs right away
|
||||||
|
when you visited a page, and had to wait for the JSON response first.
|
||||||
|
Lastly, this approach went completely parallel to the server-rendered diffs used on other pages,
|
||||||
|
which resulted in two completely separate codebases for the diffs.
|
||||||
|
|
||||||
|
The new-diffs approach changes that by doing the following:
|
||||||
|
|
||||||
|
1. Stop using virtualized scrolling for rendering diffs.
|
||||||
|
1. Move most of the rendering work to the server.
|
||||||
|
1. Enhance server-rendered HTML on the client.
|
||||||
|
1. Unify diffs codebase across merge requests and other pages.
|
||||||
|
|
||||||
### Accessibility
|
### Accessibility
|
||||||
|
|
||||||
|
|
@ -122,6 +140,42 @@ To measure our success, we need to set meaningful metrics. These metrics should
|
||||||
---
|
---
|
||||||
<sup>1</sup>: [The Performance Inequality Gap, 2023](https://infrequently.org/2022/12/performance-baseline-2023/)
|
<sup>1</sup>: [The Performance Inequality Gap, 2023](https://infrequently.org/2022/12/performance-baseline-2023/)
|
||||||
|
|
||||||
|
### New architecture overview
|
||||||
|
|
||||||
|
New diffs introduce a change in responsibilities for both frontend and backend.
|
||||||
|
|
||||||
|
The backend will:
|
||||||
|
|
||||||
|
1. Prepare diffs data.
|
||||||
|
1. Highlight diff lines.
|
||||||
|
1. Render diffs as HTML.
|
||||||
|
1. Embed diffs metadata into the final response.
|
||||||
|
|
||||||
|
The frontend will:
|
||||||
|
|
||||||
|
1. Enhance existing and future diffs HTML.
|
||||||
|
1. Fetch and render additional diffs HTML that didn't fit into the page document.
|
||||||
|
|
||||||
|
#### Static and dynamic separation
|
||||||
|
|
||||||
|
To achieve the separation of concerns, we should distinguish between static and dynamic UI on the page:
|
||||||
|
|
||||||
|
- Everything that is static should always be rendered on the server.
|
||||||
|
- Everything dynamic should be enhanced on the client.
|
||||||
|
|
||||||
|
As an example: a highlighted diff line doesn't change with user input, so we should consider rendering it on the server.
|
||||||
|
|
||||||
|
#### Performance optimizations
|
||||||
|
|
||||||
|
To improve the perceived performance of the page we should implement the following techniques:
|
||||||
|
|
||||||
|
1. Limit the number of diffs rendered on the page at first.
|
||||||
|
1. Use [HTML streaming](https://gitlab.com/gitlab-org/frontend/rfcs/-/issues/101)
|
||||||
|
to render the rest of the diffs.
|
||||||
|
1. Use Web Components to hook into diff files appearing on the page.
|
||||||
|
1. Apply `content-visibility` whenever possible to reduce redraw overhead.
|
||||||
|
1. Render diff discussions asynchronously.
|
||||||
|
|
||||||
### Front end
|
### Front end
|
||||||
|
|
||||||
#### High-level implementation
|
#### High-level implementation
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,8 @@ Notifications and mentions can be disabled in
|
||||||
When you mention a group in a comment, every member of the group gets a to-do item
|
When you mention a group in a comment, every member of the group gets a to-do item
|
||||||
added to their To-do list.
|
added to their To-do list.
|
||||||
|
|
||||||
1. Open the MR or issue.
|
1. On the left sidebar, select **Search or go to** and find your project.
|
||||||
|
1. Select **Issues** or **Merge requests**, and find your issue or merge request.
|
||||||
1. In a comment, type `@` followed by the user, group, or subgroup namespace.
|
1. In a comment, type `@` followed by the user, group, or subgroup namespace.
|
||||||
For example, `@alex`, `@alex-team`, or `@alex-team/marketing`.
|
For example, `@alex`, `@alex-team`, or `@alex-team/marketing`.
|
||||||
1. Select **Comment**.
|
1. Select **Comment**.
|
||||||
|
|
@ -96,8 +97,9 @@ persist, even when you:
|
||||||
|
|
||||||
To add a commit diff comment:
|
To add a commit diff comment:
|
||||||
|
|
||||||
1. To select a specific commit, on the merge request, select the **Commits** tab, select the commit
|
1. On the left sidebar, select **Search or go to** and find your project.
|
||||||
message. To view the latest commit, select the **Changes** tab.
|
1. Select **Merge requests**, and find your merge request.
|
||||||
|
1. Select the **Commits** tab, then select the commit message.
|
||||||
1. By the line you want to comment on, hover over the line number and select **Comment** (**{comment}**).
|
1. By the line you want to comment on, hover over the line number and select **Comment** (**{comment}**).
|
||||||
You can select multiple lines by dragging the **Comment** (**{comment}**) icon.
|
You can select multiple lines by dragging the **Comment** (**{comment}**) icon.
|
||||||
1. Enter your comment and select **Start a review** or **Add comment now**.
|
1. Enter your comment and select **Start a review** or **Add comment now**.
|
||||||
|
|
@ -157,10 +159,11 @@ Prerequisites:
|
||||||
|
|
||||||
To lock an issue or merge request:
|
To lock an issue or merge request:
|
||||||
|
|
||||||
1. On the right sidebar, next to **Lock discussion**, select **Edit**.
|
1. On the left sidebar, select **Search or go to** and find your project.
|
||||||
1. On the confirmation dialog, select **Lock**.
|
1. Select **Issues** or **Merge requests**, and find your issue or merge request.
|
||||||
|
1. On the top right, select **Merge request actions** or **Issue actions** (**{ellipsis_v}**), then select **Lock discussion**.
|
||||||
|
|
||||||
Notes are added to the page details.
|
A system note is added to the page details.
|
||||||
|
|
||||||
If an issue or merge request is closed with a locked discussion, then you cannot reopen it until the discussion is unlocked.
|
If an issue or merge request is closed with a locked discussion, then you cannot reopen it until the discussion is unlocked.
|
||||||
|
|
||||||
|
|
@ -189,7 +192,7 @@ Prerequisites:
|
||||||
|
|
||||||
To add an internal note:
|
To add an internal note:
|
||||||
|
|
||||||
1. Start adding a new comment.
|
1. On the issue or epic, in the **Comment** text box, type a comment.
|
||||||
1. Below the comment, select the **Make this an internal note** checkbox.
|
1. Below the comment, select the **Make this an internal note** checkbox.
|
||||||
1. Select **Add internal note**.
|
1. Select **Add internal note**.
|
||||||
|
|
||||||
|
|
@ -204,7 +207,7 @@ changes ([system notes](../project/system_notes.md)). System notes include chang
|
||||||
objects, or changes to labels, assignees, and the milestone.
|
objects, or changes to labels, assignees, and the milestone.
|
||||||
GitLab saves your preference, and applies it to every issue, merge request, or epic you view.
|
GitLab saves your preference, and applies it to every issue, merge request, or epic you view.
|
||||||
|
|
||||||
1. Open the **Overview** tab in a merge request, issue, or epic.
|
1. On a merge request, issue, or epic, select the **Overview** tab.
|
||||||
1. On the right side of the page, from the **Sort or filter** dropdown list, select a filter:
|
1. On the right side of the page, from the **Sort or filter** dropdown list, select a filter:
|
||||||
- **Show all activity**: Display all user comments and system notes.
|
- **Show all activity**: Display all user comments and system notes.
|
||||||
- **Show comments only**: Display only user comments.
|
- **Show comments only**: Display only user comments.
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,7 @@ module Gitlab
|
||||||
# If a newer pipeline already build a PagesDeployment
|
# If a newer pipeline already build a PagesDeployment
|
||||||
def validate_outdated_sha
|
def validate_outdated_sha
|
||||||
return if latest?
|
return if latest?
|
||||||
|
return if latest_pipeline_id.blank?
|
||||||
return if latest_pipeline_id <= build.pipeline_id
|
return if latest_pipeline_id <= build.pipeline_id
|
||||||
|
|
||||||
errors.add(:base, 'build SHA is outdated for this ref')
|
errors.add(:base, 'build SHA is outdated for this ref')
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ QA::Runtime::Browser.configure!
|
||||||
QA::Specs::Helpers::FeatureSetup.configure!
|
QA::Specs::Helpers::FeatureSetup.configure!
|
||||||
QA::Runtime::AllureReport.configure!
|
QA::Runtime::AllureReport.configure!
|
||||||
QA::Runtime::Scenario.from_env(QA::Runtime::Env.runtime_scenario_attributes)
|
QA::Runtime::Scenario.from_env(QA::Runtime::Env.runtime_scenario_attributes)
|
||||||
QA::Support::KnapsackReport.configure!
|
|
||||||
QA::Service::DockerRun::Video.configure!
|
QA::Service::DockerRun::Video.configure!
|
||||||
|
|
||||||
# Enable zero monkey patching mode before loading any other RSpec code.
|
# Enable zero monkey patching mode before loading any other RSpec code.
|
||||||
|
|
|
||||||
|
|
@ -58,16 +58,6 @@ module QA
|
||||||
File.write(selective_path, filtered_timed_specs.to_json)
|
File.write(selective_path, filtered_timed_specs.to_json)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Add '-selective-parallel' suffix to report name
|
|
||||||
#
|
|
||||||
# @return [String]
|
|
||||||
def selective_path
|
|
||||||
extension = File.extname(report_path)
|
|
||||||
directory = File.dirname(report_path)
|
|
||||||
file_name = File.basename(report_path, extension)
|
|
||||||
File.join(directory, "#{file_name}-selective-parallel#{extension}")
|
|
||||||
end
|
|
||||||
|
|
||||||
# Rename and move new regenerated report to a separate folder used to indicate report name
|
# Rename and move new regenerated report to a separate folder used to indicate report name
|
||||||
#
|
#
|
||||||
# @return [void]
|
# @return [void]
|
||||||
|
|
@ -103,7 +93,7 @@ module QA
|
||||||
report = jsons
|
report = jsons
|
||||||
.map { |json| JSON.parse(File.read(json)) }
|
.map { |json| JSON.parse(File.read(json)) }
|
||||||
.reduce({}, :merge)
|
.reduce({}, :merge)
|
||||||
.sort_by { |k, v| v } # sort report by execution time
|
.sort_by { |_k, v| v } # sort report by execution time
|
||||||
.to_h
|
.to_h
|
||||||
next logger.warn("Knapsack generated empty report for '#{name}', skipping upload!") if report.empty?
|
next logger.warn("Knapsack generated empty report for '#{name}', skipping upload!") if report.empty?
|
||||||
|
|
||||||
|
|
@ -188,6 +178,17 @@ module QA
|
||||||
|
|
||||||
{ google_json_key_string: json_key }
|
{ google_json_key_string: json_key }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Add '-selective-parallel' suffix to report name
|
||||||
|
#
|
||||||
|
# @return [String]
|
||||||
|
def selective_path
|
||||||
|
extension = File.extname(report_path)
|
||||||
|
directory = File.dirname(report_path)
|
||||||
|
file_name = File.basename(report_path, extension)
|
||||||
|
|
||||||
|
File.join(directory, "#{file_name}-selective-parallel#{extension}")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -24,16 +24,9 @@ RSpec.describe QA::Support::KnapsackReport do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates a filtered file based on qa_tests' do
|
it 'creates a filtered file based on qa_tests' do
|
||||||
expect(File).to receive(:write)
|
expect(File).to receive(:write).with('knapsack/instance-selective-parallel.json', expected_output.to_json)
|
||||||
.with('knapsack/instance-selective-parallel.json', expected_output.to_json)
|
|
||||||
|
|
||||||
knapsack_report.create_for_selective(qa_tests)
|
knapsack_report.create_for_selective(qa_tests)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#selective_path' do
|
|
||||||
it 'returns the path with file name suffixed with -selective-parallel' do
|
|
||||||
expect(knapsack_report.selective_path).to eq('knapsack/instance-selective-parallel.json')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -53,8 +53,8 @@ RSpec.describe 'User searches for code', :js, :disable_rate_limiter, feature_cat
|
||||||
let(:expected_result) { 'Update capybara, rspec-rails, poltergeist to recent versions' }
|
let(:expected_result) { 'Update capybara, rspec-rails, poltergeist to recent versions' }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
fill_in('dashboard_search', with: 'rspec')
|
submit_dashboard_search('rspec')
|
||||||
find('.gl-search-box-by-click-search-button').click
|
select_search_scope('Code')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'finds code and links to blob' do
|
it 'finds code and links to blob' do
|
||||||
|
|
@ -81,8 +81,8 @@ RSpec.describe 'User searches for code', :js, :disable_rate_limiter, feature_cat
|
||||||
search = 'for naming files'
|
search = 'for naming files'
|
||||||
ref_selector = 'v1.0.0'
|
ref_selector = 'v1.0.0'
|
||||||
|
|
||||||
fill_in('dashboard_search', with: search)
|
submit_dashboard_search(search)
|
||||||
find('.gl-search-box-by-click-search-button').click
|
select_search_scope('Code')
|
||||||
|
|
||||||
expect(page).to have_selector('.results', text: expected_result)
|
expect(page).to have_selector('.results', text: expected_result)
|
||||||
|
|
||||||
|
|
@ -99,52 +99,6 @@ RSpec.describe 'User searches for code', :js, :disable_rate_limiter, feature_cat
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when header search' do
|
|
||||||
context 'search code within refs' do
|
|
||||||
let(:ref_name) { 'v1.0.0' }
|
|
||||||
|
|
||||||
before do
|
|
||||||
visit(project_tree_path(project, ref_name))
|
|
||||||
|
|
||||||
submit_search('gitlab-grack')
|
|
||||||
wait_for_requests
|
|
||||||
select_search_scope('Code')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'shows ref switcher in code result summary' do
|
|
||||||
expect(find('.ref-selector')).to have_text(ref_name)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'persists branch name across search' do
|
|
||||||
find('.gl-search-box-by-click-search-button').click
|
|
||||||
expect(find('.ref-selector')).to have_text(ref_name)
|
|
||||||
end
|
|
||||||
|
|
||||||
# this example is use to test the design that the refs is not
|
|
||||||
# only represent the branch as well as the tags.
|
|
||||||
it 'ref switcher list all the branches and tags' do
|
|
||||||
find('.ref-selector').click
|
|
||||||
wait_for_requests
|
|
||||||
|
|
||||||
page.within('.ref-selector') do
|
|
||||||
expect(page).to have_selector('li', text: 'add-ipython-files')
|
|
||||||
expect(page).to have_selector('li', text: 'v1.0.0')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'search result changes when refs switched' do
|
|
||||||
expect(find('.results')).not_to have_content('path = gitlab-grack')
|
|
||||||
|
|
||||||
find('.ref-selector').click
|
|
||||||
wait_for_requests
|
|
||||||
|
|
||||||
select_listbox_item('add-ipython-files')
|
|
||||||
|
|
||||||
expect(page).to have_selector('.results', text: 'path = gitlab-grack')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'no ref switcher shown in issue result summary' do
|
it 'no ref switcher shown in issue result summary' do
|
||||||
issue = create(:issue, title: 'test', project: project)
|
issue = create(:issue, title: 'test', project: project)
|
||||||
visit(project_tree_path(project))
|
visit(project_tree_path(project))
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,7 @@ RSpec.describe 'User searches for issues', :js, :clean_gitlab_redis_rate_limitin
|
||||||
let!(:issue2) { create(:issue, :closed, :confidential, title: 'issue Bar', project: project) }
|
let!(:issue2) { create(:issue, :closed, :confidential, title: 'issue Bar', project: project) }
|
||||||
|
|
||||||
def search_for_issue(search)
|
def search_for_issue(search)
|
||||||
fill_in('dashboard_search', with: search)
|
submit_dashboard_search(search)
|
||||||
find('.gl-search-box-by-click-search-button').click
|
|
||||||
select_search_scope('Issues')
|
select_search_scope('Issues')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,7 @@ RSpec.describe 'User searches for merge requests', :js, :clean_gitlab_redis_rate
|
||||||
let_it_be(:merge_request2) { create(:merge_request, :simple, title: 'Merge Request Bar', source_project: project, target_project: project) }
|
let_it_be(:merge_request2) { create(:merge_request, :simple, title: 'Merge Request Bar', source_project: project, target_project: project) }
|
||||||
|
|
||||||
def search_for_mr(search)
|
def search_for_mr(search)
|
||||||
fill_in('dashboard_search', with: search)
|
submit_dashboard_search(search)
|
||||||
find('.gl-search-box-by-click-search-button').click
|
|
||||||
select_search_scope('Merge requests')
|
select_search_scope('Merge requests')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,7 @@ RSpec.describe 'User searches for milestones', :js, :clean_gitlab_redis_rate_lim
|
||||||
include_examples 'search timeouts', 'milestones'
|
include_examples 'search timeouts', 'milestones'
|
||||||
|
|
||||||
it 'finds a milestone' do
|
it 'finds a milestone' do
|
||||||
fill_in('dashboard_search', with: milestone1.title)
|
submit_dashboard_search(milestone1.title)
|
||||||
find('.gl-search-box-by-click-search-button').click
|
|
||||||
select_search_scope('Milestones')
|
select_search_scope('Milestones')
|
||||||
|
|
||||||
page.within('.results') do
|
page.within('.results') do
|
||||||
|
|
@ -41,8 +40,7 @@ RSpec.describe 'User searches for milestones', :js, :clean_gitlab_redis_rate_lim
|
||||||
select_listbox_item project.name
|
select_listbox_item project.name
|
||||||
end
|
end
|
||||||
|
|
||||||
fill_in('dashboard_search', with: milestone1.title)
|
submit_dashboard_search(milestone1.title)
|
||||||
find('.gl-search-box-by-click-search-button').click
|
|
||||||
select_search_scope('Milestones')
|
select_search_scope('Milestones')
|
||||||
|
|
||||||
page.within('.results') do
|
page.within('.results') do
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,7 @@ RSpec.describe 'User searches for projects', :js, :disable_rate_limiter, feature
|
||||||
|
|
||||||
it 'finds a project' do
|
it 'finds a project' do
|
||||||
visit(search_path)
|
visit(search_path)
|
||||||
|
submit_dashboard_search(project.name[0..3])
|
||||||
fill_in('dashboard_search', with: project.name[0..3])
|
|
||||||
click_button('Search')
|
|
||||||
|
|
||||||
expect(page).to have_link(project.name)
|
expect(page).to have_link(project.name)
|
||||||
end
|
end
|
||||||
|
|
@ -26,7 +24,7 @@ RSpec.describe 'User searches for projects', :js, :disable_rate_limiter, feature
|
||||||
it 'preserves the group being searched in' do
|
it 'preserves the group being searched in' do
|
||||||
visit(search_path(group_id: project.namespace.id))
|
visit(search_path(group_id: project.namespace.id))
|
||||||
|
|
||||||
submit_search('foo')
|
submit_dashboard_search('foo')
|
||||||
|
|
||||||
expect(find('#group_id', visible: false).value).to eq(project.namespace.id.to_s)
|
expect(find('#group_id', visible: false).value).to eq(project.namespace.id.to_s)
|
||||||
end
|
end
|
||||||
|
|
@ -34,7 +32,7 @@ RSpec.describe 'User searches for projects', :js, :disable_rate_limiter, feature
|
||||||
it 'preserves the project being searched in' do
|
it 'preserves the project being searched in' do
|
||||||
visit(search_path(project_id: project.id))
|
visit(search_path(project_id: project.id))
|
||||||
|
|
||||||
submit_search('foo')
|
submit_dashboard_search('foo')
|
||||||
|
|
||||||
expect(find('#project_id', visible: false).value).to eq(project.id.to_s)
|
expect(find('#project_id', visible: false).value).to eq(project.id.to_s)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,7 @@ RSpec.describe 'User searches for wiki pages', :js, :clean_gitlab_redis_rate_lim
|
||||||
select_listbox_item project.name
|
select_listbox_item project.name
|
||||||
end
|
end
|
||||||
|
|
||||||
fill_in('dashboard_search', with: search_term)
|
submit_dashboard_search(search_term)
|
||||||
find('.gl-search-box-by-click-search-button').click
|
|
||||||
select_search_scope('Wiki')
|
select_search_scope('Wiki')
|
||||||
|
|
||||||
page.within('.results') do
|
page.within('.results') do
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { GlSearchBoxByClick, GlButton } from '@gitlab/ui';
|
import { GlSearchBoxByType, GlButton } from '@gitlab/ui';
|
||||||
import { shallowMount } from '@vue/test-utils';
|
import { shallowMount } from '@vue/test-utils';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
|
@ -43,7 +43,7 @@ describe('GlobalSearchTopbar', () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const findGlSearchBox = () => wrapper.findComponent(GlSearchBoxByClick);
|
const findGlSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
|
||||||
const findGroupFilter = () => wrapper.findComponent(GroupFilter);
|
const findGroupFilter = () => wrapper.findComponent(GroupFilter);
|
||||||
const findProjectFilter = () => wrapper.findComponent(ProjectFilter);
|
const findProjectFilter = () => wrapper.findComponent(ProjectFilter);
|
||||||
const findSyntaxOptionButton = () => wrapper.findComponent(GlButton);
|
const findSyntaxOptionButton = () => wrapper.findComponent(GlButton);
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ require 'spec_helper'
|
||||||
RSpec.describe Gitlab::Pages::DeploymentUpdate, feature_category: :pages do
|
RSpec.describe Gitlab::Pages::DeploymentUpdate, feature_category: :pages do
|
||||||
let_it_be(:project, refind: true) { create(:project, :repository) }
|
let_it_be(:project, refind: true) { create(:project, :repository) }
|
||||||
|
|
||||||
let_it_be(:old_pipeline) { create(:ci_pipeline, project: project, sha: project.commit('HEAD').sha) }
|
let_it_be(:old_pipeline) { create(:ci_pipeline, project: project, sha: project.commit('HEAD~~').sha) }
|
||||||
let_it_be(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit('HEAD').sha) }
|
let_it_be(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit('HEAD~').sha) }
|
||||||
|
|
||||||
let(:build) { create(:ci_build, pipeline: pipeline, ref: 'HEAD') }
|
let(:build) { create(:ci_build, pipeline: pipeline, ref: 'HEAD') }
|
||||||
let(:invalid_file) { fixture_file_upload('spec/fixtures/dk.png') }
|
let(:invalid_file) { fixture_file_upload('spec/fixtures/dk.png') }
|
||||||
|
|
@ -137,4 +137,35 @@ RSpec.describe Gitlab::Pages::DeploymentUpdate, feature_category: :pages do
|
||||||
expect(pages_deployment_update).to be_valid
|
expect(pages_deployment_update).to be_valid
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when validating if current build is outdated' do
|
||||||
|
before do
|
||||||
|
create(:ci_job_artifact, :correct_checksum, file: file, job: build)
|
||||||
|
create(:ci_job_artifact, file_type: :metadata, file_format: :gzip, file: metadata, job: build)
|
||||||
|
build.reload
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when there is NOT a newer build' do
|
||||||
|
it 'does not fail' do
|
||||||
|
expect(pages_deployment_update).to be_valid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when there is a newer build' do
|
||||||
|
before do
|
||||||
|
new_pipeline = create(:ci_pipeline, project: project, sha: project.commit('HEAD').sha)
|
||||||
|
new_build = create(:ci_build, name: 'pages', pipeline: new_pipeline, ref: 'HEAD')
|
||||||
|
create(:ci_job_artifact, :correct_checksum, file: file, job: new_build)
|
||||||
|
create(:ci_job_artifact, file_type: :metadata, file_format: :gzip, file: metadata, job: new_build)
|
||||||
|
create(:pages_deployment, project: project, ci_build: new_build)
|
||||||
|
new_build.reload
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fails with outdated reference message' do
|
||||||
|
expect(pages_deployment_update).not_to be_valid
|
||||||
|
expect(pages_deployment_update.errors.full_messages)
|
||||||
|
.to include('build SHA is outdated for this ref')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,15 @@ module SearchHelpers
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def submit_dashboard_search(query)
|
||||||
|
visit(search_path) unless page.has_css?('#dashboard_search')
|
||||||
|
|
||||||
|
search_form = page.find('input[name="search"]', match: :first)
|
||||||
|
|
||||||
|
search_form.fill_in(with: query)
|
||||||
|
search_form.send_keys(:enter)
|
||||||
|
end
|
||||||
|
|
||||||
def select_search_scope(scope)
|
def select_search_scope(scope)
|
||||||
within_testid('search-filter') do
|
within_testid('search-filter') do
|
||||||
click_link scope
|
click_link scope
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue