Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-10-28 18:08:52 +00:00
parent 77d49e6a73
commit 3e49ae159a
63 changed files with 784 additions and 216 deletions

View File

@ -1,6 +1,6 @@
<script>
import { GlIcon, GlLink, GlSprintf } from '@gitlab/ui';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import createFlash from '~/flash';
import uploadDesignMutation from '../../graphql/mutations/upload_design.mutation.graphql';
import { UPLOAD_DESIGN_INVALID_FILETYPE_ERROR } from '../../utils/error_messages';
import { isValidDesignFile } from '../../utils/design_management_utils';
@ -56,7 +56,7 @@ export default {
const { files } = dataTransfer;
if (!this.isValidUpload(Array.from(files))) {
createFlash(UPLOAD_DESIGN_INVALID_FILETYPE_ERROR);
createFlash({ message: UPLOAD_DESIGN_INVALID_FILETYPE_ERROR });
return;
}

View File

@ -1,5 +1,5 @@
import { propertyOf } from 'lodash';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import createFlash, { FLASH_TYPES } from '~/flash';
import { s__ } from '~/locale';
import getDesignListQuery from '../graphql/queries/get_design_list.query.graphql';
import allVersionsMixin from './all_versions';
@ -36,20 +36,20 @@ export default {
},
result() {
if (this.$route.query.version && !this.hasValidVersion) {
createFlash(
s__(
createFlash({
message: s__(
'DesignManagement|Requested design version does not exist. Showing latest version instead',
),
);
});
this.$router.replace({ name: DESIGNS_ROUTE_NAME, query: { version: undefined } });
}
if (this.designCollection.copyState === 'ERROR') {
createFlash(
s__(
createFlash({
message: s__(
'DesignManagement|There was an error moving your designs. Please upload your designs below.',
),
'warning',
);
type: FLASH_TYPES.WARNING,
});
}
},
},

View File

@ -2,7 +2,7 @@
import Mousetrap from 'mousetrap';
import { GlLoadingIcon, GlAlert } from '@gitlab/ui';
import { ApolloMutation } from 'vue-apollo';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import createFlash from '~/flash';
import { fetchPolicies } from '~/lib/graphql';
import allVersionsMixin from '../../mixins/all_versions';
import Toolbar from '../../components/toolbar/index.vue';
@ -230,7 +230,7 @@ export default {
onQueryError(message) {
// because we redirect user to /designs (the issue page),
// we want to create these flashes on the issue page
createFlash(message);
createFlash({ message });
this.$router.push({ name: this.$options.DESIGNS_ROUTE_NAME });
},
onError(message, e) {

View File

@ -1,7 +1,7 @@
<script>
import { GlLoadingIcon, GlButton, GlAlert } from '@gitlab/ui';
import VueDraggable from 'vuedraggable';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import createFlash, { FLASH_TYPES } from '~/flash';
import { s__, sprintf } from '~/locale';
import { getFilename } from '~/lib/utils/file_upload';
import UploadButton from '../components/upload/button.vue';
@ -139,8 +139,8 @@ export default {
if (!this.canCreateDesign) return false;
if (files.length > MAXIMUM_FILE_UPLOAD_LIMIT) {
createFlash(
sprintf(
createFlash({
message: sprintf(
s__(
'DesignManagement|The maximum number of designs allowed to be uploaded is %{upload_limit}. Please try again.',
),
@ -148,7 +148,7 @@ export default {
upload_limit: MAXIMUM_FILE_UPLOAD_LIMIT,
},
),
);
});
return false;
}
@ -191,7 +191,7 @@ export default {
const skippedFiles = res?.data?.designManagementUpload?.skippedDesigns || [];
const skippedWarningMessage = designUploadSkippedWarning(this.filesToBeSaved, skippedFiles);
if (skippedWarningMessage) {
createFlash(skippedWarningMessage, 'warning');
createFlash({ message: skippedWarningMessage, types: FLASH_TYPES.WARNING });
}
// if this upload resulted in a new version being created, redirect user to the latest version
@ -214,7 +214,7 @@ export default {
},
onUploadDesignError() {
this.resetFilesToBeSaved();
createFlash(UPLOAD_DESIGN_ERROR);
createFlash({ message: UPLOAD_DESIGN_ERROR });
},
changeSelectedDesigns(filename) {
if (this.isDesignSelected(filename)) {
@ -245,18 +245,18 @@ export default {
},
onDesignDeleteError() {
const errorMessage = designDeletionError({ singular: this.selectedDesigns.length === 1 });
createFlash(errorMessage);
createFlash({ message: errorMessage });
},
onExistingDesignDropzoneChange(files, existingDesignFilename) {
const filesArr = Array.from(files);
if (filesArr.length > 1) {
createFlash(EXISTING_DESIGN_DROP_MANY_FILES_MESSAGE);
createFlash({ message: EXISTING_DESIGN_DROP_MANY_FILES_MESSAGE });
return;
}
if (!filesArr.some(({ name }) => existingDesignFilename === name)) {
createFlash(EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE);
createFlash({ message: EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE });
return;
}
@ -307,7 +307,7 @@ export default {
optimisticResponse: moveDesignOptimisticResponse(this.reorderedDesigns),
})
.catch(() => {
createFlash(MOVE_DESIGN_ERROR);
createFlash({ message: MOVE_DESIGN_ERROR });
})
.finally(() => {
this.isReorderingInProgress = false;

View File

@ -2,7 +2,7 @@
import { differenceBy } from 'lodash';
import produce from 'immer';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import createFlash from '~/flash';
import { extractCurrentDiscussion, extractDesign, extractDesigns } from './design_management_utils';
import {
ADD_IMAGE_DIFF_NOTE_ERROR,
@ -237,7 +237,7 @@ export const deletePendingTodoFromStore = (store, todoMarkDone, query, queryVari
};
const onError = (data, message) => {
createFlash(message);
createFlash({ message });
throw new Error(data.errors);
};
@ -286,7 +286,7 @@ export const updateStoreAfterUploadDesign = (store, data, query) => {
export const updateDesignsOnStoreAfterReorder = (store, data, query) => {
if (hasErrors(data)) {
createFlash(data.errors[0]);
createFlash({ message: data.errors[0] });
} else {
moveDesignInStore(store, data, query);
}

View File

@ -1,11 +1,11 @@
<script>
import { GlFormGroup, GlDeprecatedDropdown, GlDeprecatedDropdownItem } from '@gitlab/ui';
import { GlFormGroup, GlDropdown, GlDropdownItem } from '@gitlab/ui';
export default {
components: {
GlFormGroup,
GlDeprecatedDropdown,
GlDeprecatedDropdownItem,
GlDropdown,
GlDropdownItem,
},
props: {
name: {
@ -41,16 +41,13 @@ export default {
</script>
<template>
<gl-form-group :label="label">
<gl-deprecated-dropdown
toggle-class="dropdown-menu-toggle"
:text="text || s__('Metrics|Select a value')"
>
<gl-deprecated-dropdown-item
<gl-dropdown toggle-class="dropdown-menu-toggle" :text="text || s__('Metrics|Select a value')">
<gl-dropdown-item
v-for="val in options.values"
:key="val.value"
@click="onUpdate(val.value)"
>{{ val.text }}</gl-deprecated-dropdown-item
>{{ val.text }}</gl-dropdown-item
>
</gl-deprecated-dropdown>
</gl-dropdown>
</gl-form-group>
</template>

View File

@ -1,4 +1,5 @@
import $ from 'jquery';
import setHighlightClass from 'ee_else_ce/search/highlight_blob_search_result';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
import { deprecatedCreateFlash as Flash } from '~/flash';
import Api from '~/api';
@ -6,7 +7,6 @@ import { __ } from '~/locale';
import Project from '~/pages/projects/project';
import { visitUrl } from '~/lib/utils/url_utility';
import refreshCounts from './refresh_counts';
import setHighlightClass from './highlight_blob_search_result';
export default class Search {
constructor() {

View File

@ -1,8 +1,7 @@
<script>
/* eslint-disable vue/no-v-html */
import { groupBy } from 'lodash';
import { GlIcon, GlLoadingIcon } from '@gitlab/ui';
import tooltip from '~/vue_shared/directives/tooltip';
import { GlIcon, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
import { glEmojiTag } from '../../emoji';
import { __, sprintf } from '~/locale';
@ -15,7 +14,7 @@ export default {
GlLoadingIcon,
},
directives: {
tooltip,
GlTooltip: GlTooltipDirective,
},
props: {
awards: {
@ -154,10 +153,9 @@ export default {
<button
v-for="awardList in groupedAwards"
:key="awardList.name"
v-tooltip
v-gl-tooltip.viewport
:class="awardList.classes"
:title="awardList.title"
data-boundary="viewport"
data-testid="award-button"
class="btn award-control"
type="button"
@ -168,12 +166,11 @@ export default {
</button>
<div v-if="canAwardEmoji" class="award-menu-holder">
<button
v-tooltip
v-gl-tooltip.viewport
:class="addButtonClass"
class="award-control btn js-add-award"
title="Add reaction"
:aria-label="__('Add reaction')"
data-boundary="viewport"
type="button"
>
<span class="award-control-icon award-control-icon-neutral">

View File

@ -38,11 +38,7 @@ module Analytics
scope :with_identifier, -> (identifier) { where(identifier: identifier) }
def self.measurement_identifier_values
if Feature.enabled?(:store_ci_pipeline_counts_by_status, default_enabled: true)
identifiers.values
else
identifiers.values - EXPERIMENTAL_IDENTIFIERS.map { |identifier| identifiers[identifier] }
end
identifiers.values
end
end
end

View File

@ -45,6 +45,10 @@ module Ci
def get_store_class(store)
@stores ||= {}
# Can't memoize this because the feature flag may alter this
return fog_store_class.new if store.to_sym == :fog
@stores[store] ||= "Ci::BuildTraceChunks::#{store.capitalize}".constantize.new
end
@ -74,6 +78,14 @@ module Ci
def metadata_attributes
attribute_names - %w[raw_data]
end
def fog_store_class
if Feature.enabled?(:ci_trace_new_fog_store)
Ci::BuildTraceChunks::Fog
else
Ci::BuildTraceChunks::LegacyFog
end
end
end
def data

View File

@ -8,13 +8,17 @@ module Ci
end
def data(model)
connection.get_object(bucket_name, key(model))[:body]
files.get(key(model))&.body
rescue Excon::Error::NotFound
# If the object does not exist in the object storage, this method returns nil.
end
def set_data(model, new_data)
connection.put_object(bucket_name, key(model), new_data)
# TODO: Support AWS S3 server side encryption
files.create({
key: key(model),
body: new_data
})
end
def append_data(model, new_data, offset)
@ -43,7 +47,7 @@ module Ci
def delete_keys(keys)
keys.each do |key|
connection.delete_object(bucket_name, key_raw(*key))
files.destroy(key_raw(*key))
end
end
@ -69,6 +73,14 @@ module Ci
@connection ||= ::Fog::Storage.new(object_store.connection.to_hash.deep_symbolize_keys)
end
def fog_directory
@fog_directory ||= connection.directories.new(key: bucket_name)
end
def files
@files ||= fog_directory.files
end
def object_store
Gitlab.config.artifacts.object_store
end

View File

@ -0,0 +1,77 @@
# frozen_string_literal: true
module Ci
module BuildTraceChunks
class LegacyFog
def available?
object_store.enabled
end
def data(model)
connection.get_object(bucket_name, key(model))[:body]
rescue Excon::Error::NotFound
# If the object does not exist in the object storage, this method returns nil.
end
def set_data(model, new_data)
connection.put_object(bucket_name, key(model), new_data)
end
def append_data(model, new_data, offset)
if offset > 0
truncated_data = data(model).to_s.byteslice(0, offset)
new_data = truncated_data + new_data
end
set_data(model, new_data)
new_data.bytesize
end
def size(model)
data(model).to_s.bytesize
end
def delete_data(model)
delete_keys([[model.build_id, model.chunk_index]])
end
def keys(relation)
return [] unless available?
relation.pluck(:build_id, :chunk_index)
end
def delete_keys(keys)
keys.each do |key|
connection.delete_object(bucket_name, key_raw(*key))
end
end
private
def key(model)
key_raw(model.build_id, model.chunk_index)
end
def key_raw(build_id, chunk_index)
"tmp/builds/#{build_id.to_i}/chunks/#{chunk_index.to_i}.log"
end
def bucket_name
return unless available?
object_store.remote_directory
end
def connection
return unless available?
@connection ||= ::Fog::Storage.new(object_store.connection.to_hash.deep_symbolize_keys)
end
def object_store
Gitlab.config.artifacts.object_store
end
end
end
end

View File

@ -16,8 +16,8 @@ module ProjectServicesLoggable
def build_message(message, params = {})
{
service_class: self.class.name,
project_id: project.id,
project_path: project.full_path,
project_id: project&.id,
project_path: project&.full_path,
message: message
}.merge(params)
end

View File

@ -6,7 +6,7 @@
- if verification_enabled && domain_presenter.unverified?
= content_for :flash_message do
.alert.alert-warning
.gl-alert.gl-alert-warning
.container-fluid.container-limited
= _("This domain is not verified. You will need to verify ownership before access is enabled.")

View File

@ -7,4 +7,4 @@
= search_blob_title(project, path)
- if blob.data
.file-content.code.term{ data: { qa_selector: 'file_text_content' } }
= render 'shared/file_highlight', blob: blob, first_line_number: blob.startline, blob_link: blob_link
= render 'shared/file_highlight', blob: blob, first_line_number: blob.startline, blob_link: blob_link, highlight_line: blob.highlight_line

View File

@ -1,16 +1,17 @@
#blob-content.file-content.code.js-syntax-highlight
- offset = defined?(first_line_number) ? first_line_number : 1
.line-numbers
- if blob.data.present?
- link_icon = sprite_icon('link', size: 12)
- link = blob_link if defined?(blob_link)
- blob.data.each_line.each_with_index do |_, index|
- offset = defined?(first_line_number) ? first_line_number : 1
- i = index + offset
-# We're not using `link_to` because it is too slow once we get to thousands of lines.
%a.diff-line-num{ href: "#{link}#L#{i}", id: "L#{i}", 'data-line-number' => i }
= link_icon
= i
.blob-content{ data: { blob_id: blob.id, path: blob.path } }
- highlight = defined?(highlight_line) && highlight_line ? highlight_line - offset : nil
.blob-content{ data: { blob_id: blob.id, path: blob.path, highlight_line: highlight } }
%pre.code.highlight
%code
= blob.present.highlight

View File

@ -0,0 +1,5 @@
---
title: Fix exception when saving Jira integration info for an instance
merge_request: 45718
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Allow user snippets to be indexed by search crawlers
merge_request: 45793
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Replace-GlDeprecatedDropdown-with-GlDropdown-in-app/assets/javascripts/monitoring
merge_request: 41422
author: nuwe1
type: other

View File

@ -0,0 +1,5 @@
---
title: Download LFS files when importing from Bitbucket Server
merge_request: 45908
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Migrate tooltip in app/assets/javascripts/vue_shared/components/awards_list.vue
merge_request: 46171
author:
type: other

View File

@ -1,7 +1,7 @@
---
name: store_ci_pipeline_counts_by_status
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43027
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/254721
name: ci_trace_new_fog_store
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46209
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/273405
type: development
group: group::analytics
default_enabled: true
group: group::testing
default_enabled: false

View File

@ -2,7 +2,7 @@
PRODUCT_ANALYTICS_CHANGED_FILES_MESSAGE = <<~MSG
For the following files, a review from the [Data team and Product Analytics team](https://gitlab.com/groups/gitlab-org/growth/product_analytics/engineers/-/group_members?with_inherited_permissions=exclude) is recommended
Please check the ~"product analytics(telemetry)" [guide](https://docs.gitlab.com/ee/development/product_analytics/usage_ping.html) and reach out to %<product_analytics_engineers_group>s group for a review.
Please check the ~"product analytics" [guide](https://docs.gitlab.com/ee/development/product_analytics/usage_ping.html) and reach out to %<product_analytics_engineers_group>s group for a review.
%<changed_files>s
@ -41,8 +41,8 @@ if changed_files.any?
warn format(PRODUCT_ANALYTICS_CHANGED_FILES_MESSAGE, changed_files: helper.markdown_list(changed_files), product_analytics_engineers_group: mention)
warn format(UPDATE_METRICS_DEFINITIONS_MESSAGE) unless helper.changed_files(/usage_ping\.md/).any?
product_analytics_labels = ['product analytics(telemetry)']
product_analytics_labels << 'product analytics(telemetry)::review pending' unless helper.mr_has_labels?('product analytics(telemetry)::reviewed')
product_analytics_labels = ['product analytics']
product_analytics_labels << 'product analytics::review pending' unless helper.mr_has_labels?('product analytics::reviewed')
markdown(helper.prepare_labels_for_mr(product_analytics_labels))
end

View File

@ -14,5 +14,5 @@ link: https://docs.gitlab.com/ee/development/documentation/styleguide.html#alert
level: error
scope: raw
raw:
- '((NOTE|TIP|CAUTION|DANGER): \*\*.*\*\*.+)|'
- '(\n(NOTE|TIP|CAUTION|DANGER): \*\*.*\*\*.+)|'
- '((\n[> ]*(\*){1,2}(NOTE|Note|note|TIP|Tip|tip|CAUTION|Caution|caution|DANGER|Danger|danger):(\*){1,2}))'

View File

@ -5,9 +5,10 @@
#
# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: substitution
message: 'Use "%s" instead of "%s" unless referring to the "Add a To Do" button in the UI.'
message: 'Use "to-do item" in most cases, or "Add a to do" if referring to the UI button.'
link: https://docs.gitlab.com/ee/development/documentation/styleguide.html#feature-names
level: warning
ignorecase: true
ignorecase: false
swap:
'to dos': to-do items
'[Tt]o [Dd]o [Ii]tems?': to-do item
'\w* [Aa] [Tt]o [Dd]o': Add a to do

View File

@ -424,10 +424,10 @@ DELETE /groups/:id/epics/:epic_iid
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/1/epics/5"
```
## Create a to do
## Create a to-do item
Manually creates a to do for the current user on an epic. If
there already exists a to do for the user on that epic, status code `304` is
Manually creates a to-do item for the current user on an epic. If
there already exists a to-do item for the user on that epic, status code `304` is
returned.
```plaintext

View File

@ -1507,10 +1507,10 @@ Example response:
}
```
## Create a to do
## Create a to-do item
Manually creates a to do for the current user on an issue. If
there already exists a to do for the user on that issue, status code `304` is
Manually creates a to-do item for the current user on an issue. If
there already exists a to-do item for the user on that issue, status code `304` is
returned.
```plaintext

View File

@ -2089,10 +2089,10 @@ the `approvals_before_merge` parameter:
}
```
## Create a to do
## Create a to-do item
Manually creates a to do for the current user on a merge request.
If there already exists a to do for the user on that merge request,
Manually creates a to-do item for the current user on a merge request.
If there already exists a to-do item for the user on that merge request,
status code `304` is returned.
```plaintext

View File

@ -26,7 +26,7 @@ Parameters:
| `project_id` | integer | no | The ID of a project |
| `group_id` | integer | no | The ID of a group |
| `state` | string | no | The state of the to do. Can be either `pending` or `done` |
| `type` | string | no | The type of a to do. Can be either `Issue`, `MergeRequest`, `DesignManagement::Design` or `AlertManagement::Alert` |
| `type` | string | no | The type of to-do item. Can be either `Issue`, `MergeRequest`, `DesignManagement::Design` or `AlertManagement::Alert` |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/todos"
@ -187,7 +187,7 @@ Example Response:
]
```
## Mark a to do as done
## Mark a to-do item as done
Marks a single pending to do given by its ID for the current user as done. The
to do marked as done is returned in the response.
@ -200,7 +200,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a to do |
| `id` | integer | yes | The ID of to-do item |
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/todos/130/mark_as_done"

View File

@ -186,19 +186,19 @@ After completing their portion of investigating or fixing the alert, users can
unassign themselves from the alert. To remove an assignee, select **Edit** next to the **Assignee** dropdown menu
and deselect the user from the list of assignees, or select **Unassigned**.
### Create a to do from an alert
### Create a to-do item from an alert
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3066) in GitLab 13.1.
You can manually create [To-Do list items](../../user/todos.md) for yourself
from the Alert details screen, and view them later on your **To-Do List**. To
add a to do:
add a to-do item:
1. To display the list of current alerts, navigate to **Operations > Alerts**.
1. Select your desired alert to display its **Alert Management Details View**.
1. Select the **Add a To-Do** button in the right sidebar:
![Alert Details Add A To Do](./img/alert_detail_add_todo_v13_1.png)
![Alert Details Add a To-Do](./img/alert_detail_add_todo_v13_1.png)
Select the **To-Do List** **{todo-done}** in the navigation bar to view your current to-do list.

View File

@ -190,7 +190,7 @@ adds a `missed::SLA` label to the incident.
## Incident Actions
There are different actions avilable to help triage and respond to incidents.
There are different actions available to help triage and respond to incidents.
### Assign incidents
@ -200,9 +200,9 @@ Assign incidents to users that are actively responding. Select **Edit** in the r
See [Incident List](#incident-list) for a full description of the severities available. Select **Edit** in the right-hand side bar to change the severity of an incident.
### Add a to do
### Add a to-do item
Add a to-do for incidents that you want to track in your to-do list. Clicke the **Add a to do** button at the top of the right-hand side bar to add a to do.
Add a to-do for incidents that you want to track in your to-do list. Click the **Add a to do** button at the top of the right-hand side bar to add a to-do item.
### Manage incidents from Slack

View File

@ -47,3 +47,32 @@ If you see a **Revoke** button, you can revoke that user's PAT. Whether you see
You can **Delete** a user's SSH key by navigating to the credentials inventory's SSH Keys tab.
![Credentials inventory page - SSH keys](img/credentials_inventory_ssh_keys_v13_5.png)
## Revocation or deletion notification
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/250354) in GitLab 13.6.
> - It's [deployed behind a feature flag](../../user/feature_flags.md), disabled by default.
> - It's disabled on GitLab.com.
> - It's not recommended for production use.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-revocation-or-deletion-notification).
CAUTION: **Warning:**
This feature might not be available to you. Check the **version history** note above for details.
### Enable or disable revocation or deletion notification **(ULTIMATE ONLY)**
Revocation or deletion notification is under development and not ready for production use. It is deployed behind a feature flag that is **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
can enable it.
To enable it:
```ruby
Feature.enable(:credentials_inventory_revocation_emails)
```
To disable it:
```ruby
Feature.disable(:credentials_inventory_revocation_emails)
```

View File

@ -27,16 +27,16 @@ be prompted to activate your U2F device (usually by pressing a button on it),
and it will perform secure authentication on your behalf.
It is highly recommended that you set up 2FA with both a
[one-time password authenticator](#enable-2fa-via-one-time-password-authenticator)
and a [U2F device](#enable-2fa-via-u2f-device), so you can still access your account
if you lose your U2F device.
[one-time password authenticator](#one-time-password) or use [FortiAuthenticator](#one-time-password-via-fortiauthenticator)
and a [U2F device](#u2f-device), so you can still access your account if you
lose your U2F device.
## Enabling 2FA
There are two ways to enable two-factor authentication: via a one time password authenticator
or a U2F device.
### Enable 2FA via one time password authenticator
### One-time password
To enable 2FA:
@ -66,7 +66,81 @@ two-factor authentication has been enabled, and you'll be presented with a list
of [recovery codes](#recovery-codes). Make sure you download them and keep them
in a safe place.
### Enable 2FA via U2F device
### One-time password via FortiAuthenticator
> - Introduced in [GitLab 13.5](https://gitlab.com/gitlab-org/gitlab/-/issues/212312)
> - It's deployed behind a feature flag, disabled by default.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-fortiauthenticator-integration).
You can use FortiAuthenticator as an OTP provider in GitLab. Users must exist in
both FortiAuthenticator and GitLab with the exact same username, and users must
have FortiToken configured in FortiAuthenticator.
You'll also need a username and access token for FortiAuthenticator. The
`access_token` in the code samples shown below is the FortAuthenticator access
key. To get the token, see the `REST API Solution Guide` at
[`Fortinet Document Library`](https://docs.fortinet.com/document/fortiauthenticator/6.2.0/rest-api-solution-guide/158294/the-fortiauthenticator-api).
GitLab 13.5 has been tested with FortAuthenticator version 6.2.0.
First configure FortiAuthenticator in GitLab. On your GitLab server:
1. Open the configuration file.
For Omnibus GitLab:
```shell
sudo editor /etc/gitlab/gitlab.rb
```
For installations from source:
```shell
cd /home/git/gitlab
sudo -u git -H editor config/gitlab.yml
```
1. Add the provider configuration:
For Omnibus package:
```ruby
gitlab_rails['forti_authenticator_enabled'] = true
gitlab_rails['forti_authenticator_host'] = 'forti_authenticator.example.com'
gitlab_rails['forti_authenticator_port'] = 443
gitlab_rails['forti_authenticator_username'] = '<some_username>'
gitlab_rails['forti_authenticator_access_token'] = 's3cr3t'
```
For installations from source:
```yaml
forti_authenticator:
enabled: true
host: forti_authenticator.example.com
port: 443
username: <some_username>
access_token: s3cr3t
```
1. Save the configuration file.
1. [Reconfigure](../../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure)
or [restart GitLab](../../../administration/restart_gitlab.md#installations-from-source)
for the changes to take effect if you installed GitLab via Omnibus or from
source respectively.
#### Enable FortiAuthenticator integration
This feature comes with the `:forti_authenticator` feature flag disabled by
default.
To enable this feature, ask a GitLab administrator with [Rails console access](../../../administration/feature_flags.md#how-to-enable-and-disable-features-behind-flags)
to run the following command:
```ruby
Feature.enable(:forti_authenticator, User.find(<user ID>))
```
### U2F device
> Introduced in [GitLab 8.9](https://about.gitlab.com/blog/2016/06/22/gitlab-adds-support-for-u2f/).

View File

@ -228,7 +228,7 @@ available in the **Resolved Comment** area at the bottom of the right sidebar.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/198439) in GitLab 13.4.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/245074) in GitLab 13.5.
Add a to do for a design by clicking **Add a To Do** on the design sidebar:
Add a to-do item for a design by clicking **Add a to do** on the design sidebar:
![To-do button](img/design_todo_button_v13_5.png)

View File

@ -265,6 +265,25 @@ Once the Code Quality job has completed:
[downloadable artifact](../../../ci/pipelines/job_artifacts.md#downloading-artifacts)
for the `code_quality` job.
### Generating an HTML report
In [GitLab 13.6 and later](https://gitlab.com/gitlab-org/ci-cd/codequality/-/issues/10),
it is possible to generate an HTML report file by setting the `REPORT_FORMAT`
variable to `html`. This is useful if you just want to view the report in a more
human-readable format or to publish this artifact on GitLab Pages for even
easier reviewing.
```yaml
include:
- template: Code-Quality.gitlab-ci.yml
code_quality:
variables:
REPORT_FORMAT: html
artifacts:
paths: [gl-code-quality-report.html]
```
## Extending functionality
### Using Analysis Plugins

View File

@ -0,0 +1,73 @@
# Export Merge Requests to CSV **(CORE)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/3619) in GitLab 13.6.
> - It's [deployed behind a feature flag](../../../administration/feature_flags.md), disabled by default.
> - It's disabled on GitLab.com.
> - It's not recommended for production use.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-export-merge-requests-to-csv). **(CORE ONLY)**
CAUTION: **Warning:**
This feature might not be available to you. Check the **version history** note above for details.
Exporting Merge Requests CSV enables you and your team to export all the data collected from merge requests into a comma-separated values (CSV) file, which stores tabular data in plain text.
To export Merge Requests to CSV, navigate to your **Merge Requests** from the sidebar of a project and click **Export to CSV**.
Exported files are generated asynchronously and delivered as an email attachment upon generation.
## CSV Output
The following table shows what attributes will be present in the CSV.
| Column | Description |
|--------------------|--------------------------------------------------------------|
| MR ID | MR iid |
| URL | A link to the merge request on GitLab |
| Title | Merge request title |
| State | Opened, Closed, Locked, or Merged |
| Description | Merge request description |
| Source Branch | Source branch |
| Target Branch | Target branch |
| Source Project ID | ID of the source project |
| Target Project ID | ID of the target project |
| Author | Full name of the merge request author |
| Author Username | Username of the author, with the @ symbol omitted |
| Assignees | Full names of the merge request assignees, joined with a `,` |
| Assignee Usernames | Username of the assignees, with the @ symbol omitted |
| Approvers | Full names of the approvers, joined with a `,` |
| Approver Usernames | Username of the approvers, with the @ symbol omitted |
| Merged User | Full name of the merged user |
| Merged Username | Username of the merge user, with the @ symbol omitted |
| Milestone ID | ID of the merge request milestone |
| Created At (UTC) | Formatted as YYYY-MM-DD HH:MM:SS |
| Updated At (UTC) | Formatted as YYYY-MM-DD HH:MM:SS |
## Limitations
- Export merge requests to CSV is not available at the Groups merge request list.
- As the merge request CSV file is sent as an email attachment, the size is limited to 15MB to ensure successful delivery across a range of email providers. If you need to minimize the size of the file, you can narrow the search before export. For example, you can set up exports of open and closed merge requests in separate files.
### Enable or disable Export Merge Requests to CSV **(CORE ONLY)**
Export merge requests to CSV is under development and not ready for production use. It is
deployed behind a feature flag that is **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
can enable it.
To enable it:
```ruby
Feature.enable(:export_merge_requests_as_csv)
```
To disable it:
```ruby
Feature.enable(:export_merge_requests_as_csv)
```
Optionally, pass a project as an argument to enable for a single project.
```ruby
Feature.enable(:export_merge_requests_as_csv, project)
```

View File

@ -74,7 +74,7 @@ The following quick actions are applicable to descriptions, discussions and thre
| `/tableflip <comment>` | ✓ | ✓ | ✓ | Append the comment with `(╯°□°)╯︵ ┻━┻`. |
| `/target_branch <local branch name>` | | ✓ | | Set target branch. |
| `/title <new title>` | ✓ | ✓ | ✓ | Change title. |
| `/todo` | ✓ | ✓ | ✓ | Add a to do. |
| `/todo` | ✓ | ✓ | ✓ | Add a to-do item. |
| `/unassign @user1 @user2` | ✓ | ✓ | | Remove specific assignees. **(STARTER)** |
| `/unassign` | ✓ | ✓ | | Remove all assignees. |
| `/unlabel ~label1 ~label2` or `/remove_label ~label1 ~label2` | ✓ | ✓ | ✓ | Remove specified labels. |

View File

@ -15,9 +15,9 @@ spend your time. This can include taking an action, or keeping track of things
do your work, being able to get started quickly is important.
Your *To-Do List* offers a chronological list of items waiting for your input
(known as *to do items*) in a single dashboard.
(known as *to-do items*) in a single dashboard.
The To-Do List supports tracking [actions](#what-triggers-a-to-do) related to
The To-Do List supports tracking [actions](#what-triggers-a-to-do-item) related to
the following:
- Issues
@ -27,17 +27,17 @@ the following:
![to-do screenshot showing a list of items to check on](img/todos_index.png)
You can access your To-Do List by clicking the To-Do List icon (**{task-done}**)
next to the search bar in the top navigation. If the to do item count is:
next to the search bar in the top navigation. If the to-do item count is:
- *Less than 100*, the number in blue is the number of to do items.
- *Less than 100*, the number in blue is the number of to-do items.
- *100 or more*, the number displays as 99+. The exact number displays in the
To-Do List.
![To Do icon](img/todos_icon.png)
## What triggers a to do
## What triggers a to-do item
A to do item appears on your To-Do List when:
A to-do item appears on your To-Do List when:
- An issue or merge request is assigned to you.
- You're `@mentioned` in the description or comment of an issue or merge request
@ -60,19 +60,19 @@ When several trigger actions occur for the same user on the same object (for
example, an issue), GitLab displays only the first action as a single to do
item.
To do item triggers aren't affected by [GitLab notification email settings](profile/notifications.md).
To-do item triggers aren't affected by [GitLab notification email settings](profile/notifications.md).
NOTE: **Note:**
When a user no longer has access to a resource related to a to do item (such as
When a user no longer has access to a resource related to a to-do item (such as
an issue, merge request, project, or group), for security reasons GitLab
deletes any related to do items within the next hour. Deletion is delayed to
deletes any related to-do items within the next hour. Deletion is delayed to
prevent data loss, in the case where a user's access is accidentally revoked.
### Directly addressing a to do
### Directly addressing a to-do item
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/7926) in GitLab 9.0.
If you're mentioned at the start of a line, the to do item you receive will be
If you're mentioned at the start of a line, the to-do item you receive will be
listed as *directly addressed*. For example, in the following comment:
```markdown
@ -87,11 +87,11 @@ listed as *directly addressed*. For example, in the following comment:
@erin @frank thank you!
```
The people receiving directly addressed to do items are `@alice`, `@erin`, and
`@frank`. Directly addressed to do items only differ from mentions in their type
The people receiving directly addressed to-do items are `@alice`, `@erin`, and
`@frank`. Directly addressed to-do items only differ from mentions in their type
for filtering purposes; otherwise, they appear as normal.
### Manually creating a to do
### Manually creating a to-do item
You can also add the following to your To-Do List by clicking the **Add a to do** button on an:
@ -100,14 +100,14 @@ You can also add the following to your To-Do List by clicking the **Add a to do*
- [Epic](group/epics/index.md) **(ULTIMATE)**
- [Design](project/issues/design_management.md)
![Adding a To Do from the issuable sidebar](img/todos_add_todo_sidebar.png)
![Adding a to-do item from the issuable sidebar](img/todos_add_todo_sidebar.png)
## Marking a to do as done
## Marking a to-do item as done
Any action to an issue or merge request (or epic **(ULTIMATE)**) will mark its
corresponding to do item as done.
corresponding to-do item as done.
Actions that dismiss to do items include:
Actions that dismiss to-do items include:
- Changing the assignee
- Changing the milestone
@ -115,28 +115,28 @@ Actions that dismiss to do items include:
- Commenting on the issue
Your To-Do List is personal, and items are only marked as done if you take
action. If you close the issue or merge request, your to do item is marked as
action. If you close the issue or merge request, your to-do item is marked as
done.
To prevent other users from closing issues without you being notified, if
someone else closes, merges, or takes action on an issue or merge request (or
epic **(ULTIMATE)**), your to do item remains pending.
epic **(ULTIMATE)**), your to-do item remains pending.
There's just one to do item for each of these, so mentioning a user many times
in an issue only triggers one to do item.
There's just one to-do item for each of these, so mentioning a user many times
in an issue only triggers one to-do item.
If no action is needed, you can manually mark the to do item as done by
If no action is needed, you can manually mark the to-do item as done by
clicking its corresponding **Done** button to have GitLab remove the item from
your To-Do List.
![A to do in the To-Do List](img/todos_todo_list_item.png)
You can also mark a to do item as done by clicking the **Mark as done** button
You can also mark a to-do item as done by clicking the **Mark as done** button
in the sidebar of an issue or merge request (or epic **(ULTIMATE)**).
![Mark as done from the issuable sidebar](img/todos_mark_done_sidebar.png)
You can mark all your to do items as done at once by clicking the
You can mark all your to-do items as done at once by clicking the
**Mark all as done** button.
## Filtering your To-Do List
@ -152,7 +152,7 @@ You can use the following types of filters with your To-Do List:
| Action | Filter by the action that triggered the to do. |
You can also filter by more than one of these at the same time. The previously
described [triggering actions](#what-triggers-a-to-do) include:
described [triggering actions](#what-triggers-a-to-do-item) include:
- Any action
- Assigned

View File

@ -41,6 +41,7 @@ module Gitlab
def execute
import_repository
import_pull_requests
download_lfs_objects
delete_temp_branches
handle_errors
metrics.track_finished_import
@ -148,6 +149,14 @@ module Gitlab
raise
end
def download_lfs_objects
result = Projects::LfsPointers::LfsImportService.new(project).execute
if result[:status] == :error
errors << { type: :lfs_objects, errors: "The Lfs import process failed. #{result[:message]}" }
end
end
# Bitbucket Server keeps tracks of references for open pull requests in
# refs/heads/pull-requests, but closed and merged requests get moved
# into hidden internal refs under stash-refs/pull-requests. Unless the

View File

@ -34,6 +34,7 @@ code_quality:
CODECLIMATE_DEBUG \
CODECLIMATE_DEV \
REPORT_STDOUT \
REPORT_FORMAT \
ENGINE_MEMORY_LIMIT_BYTES \
) \
--volume "$PWD":/code \

View File

@ -9,7 +9,7 @@ module Gitlab
include Gitlab::Utils::StrongMemoize
include BlobActiveModel
attr_reader :project, :content_match, :blob_path
attr_reader :project, :content_match, :blob_path, :highlight_line
PATH_REGEXP = /\A(?<ref>[^:]*):(?<path>[^\x00]*)\x00/.freeze
CONTENT_REGEXP = /^(?<ref>[^:]*):(?<path>[^\x00]*)\x00(?<startline>\d+)\x00/.freeze
@ -26,6 +26,7 @@ module Gitlab
@binary_basename = opts.fetch(:basename, nil)
@ref = opts.fetch(:ref, nil)
@startline = opts.fetch(:startline, nil)
@highlight_line = opts.fetch(:highlight_line, nil)
@binary_data = opts.fetch(:data, nil)
@per_page = opts.fetch(:per_page, 20)
@project = opts.fetch(:project, nil)

View File

@ -24,8 +24,9 @@ Disallow: /help
Disallow: /s/
Disallow: /-/profile
Disallow: /-/ide/
# Only specifically allow the Sign In page to avoid very ugly search results
# Restrict allowed routes to avoid very ugly search results
Allow: /users/sign_in
Allow: /users/*/snippets
# Generic resource routes like new, edit, raw
# This will block routes like:

View File

@ -26,7 +26,7 @@ RSpec.describe 'Issues > User sees live update', :js do
end
describe 'confidential issue#show' do
it 'shows confidential sibebar information as confidential and can be turned off' do
it 'shows confidential sibebar information as confidential and can be turned off', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/254644' do
issue = create(:issue, :confidential, project: project)
visit project_issue_path(project, issue)

View File

@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
import DesignDropzone from '~/design_management/components/upload/design_dropzone.vue';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import createFlash from '~/flash';
jest.mock('~/flash');

View File

@ -2,7 +2,7 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueRouter from 'vue-router';
import { GlAlert } from '@gitlab/ui';
import { ApolloMutation } from 'vue-apollo';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import createFlash from '~/flash';
import DesignIndex from '~/design_management/pages/design/index.vue';
import DesignSidebar from '~/design_management/components/design_sidebar.vue';
import DesignPresentation from '~/design_management/components/design_presentation.vue';
@ -295,7 +295,7 @@ describe('Design management design index page', () => {
wrapper.vm.onDesignQueryResult({ data: mockResponseNoDesigns, loading: false });
return wrapper.vm.$nextTick().then(() => {
expect(createFlash).toHaveBeenCalledTimes(1);
expect(createFlash).toHaveBeenCalledWith(DESIGN_NOT_FOUND_ERROR);
expect(createFlash).toHaveBeenCalledWith({ message: DESIGN_NOT_FOUND_ERROR });
expect(router.push).toHaveBeenCalledTimes(1);
expect(router.push).toHaveBeenCalledWith({ name: DESIGNS_ROUTE_NAME });
});
@ -316,7 +316,7 @@ describe('Design management design index page', () => {
wrapper.vm.onDesignQueryResult({ data: mockResponseWithDesigns, loading: false });
return wrapper.vm.$nextTick().then(() => {
expect(createFlash).toHaveBeenCalledTimes(1);
expect(createFlash).toHaveBeenCalledWith(DESIGN_VERSION_NOT_EXIST_ERROR);
expect(createFlash).toHaveBeenCalledWith({ message: DESIGN_VERSION_NOT_EXIST_ERROR });
expect(router.push).toHaveBeenCalledTimes(1);
expect(router.push).toHaveBeenCalledWith({ name: DESIGNS_ROUTE_NAME });
});

View File

@ -16,7 +16,7 @@ import {
EXISTING_DESIGN_DROP_MANY_FILES_MESSAGE,
EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE,
} from '~/design_management/utils/error_messages';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import createFlash from '~/flash';
import createRouter from '~/design_management/router';
import * as utils from '~/design_management/utils/design_management_utils';
import {
@ -443,10 +443,10 @@ describe('Design management index page', () => {
return uploadDesign.then(() => {
expect(createFlash).toHaveBeenCalledTimes(1);
expect(createFlash).toHaveBeenCalledWith(
'Upload skipped. test.jpg did not change.',
'warning',
);
expect(createFlash).toHaveBeenCalledWith({
message: 'Upload skipped. test.jpg did not change.',
types: 'warning',
});
});
});
@ -482,7 +482,7 @@ describe('Design management index page', () => {
designDropzone.vm.$emit('change', eventPayload);
expect(createFlash).toHaveBeenCalledTimes(1);
expect(createFlash).toHaveBeenCalledWith(message);
expect(createFlash).toHaveBeenCalledWith({ message });
});
});
@ -747,7 +747,7 @@ describe('Design management index page', () => {
await wrapper.vm.$nextTick();
expect(createFlash).toHaveBeenCalledWith('Houston, we have a problem');
expect(createFlash).toHaveBeenCalledWith({ message: 'Houston, we have a problem' });
});
it('displays flash if mutation had a non-recoverable error', async () => {
@ -761,9 +761,9 @@ describe('Design management index page', () => {
await jest.runOnlyPendingTimers(); // kick off the mocked GQL stuff (promises)
await wrapper.vm.$nextTick(); // kick off the DOM update for flash
expect(createFlash).toHaveBeenCalledWith(
'Something went wrong when reordering designs. Please try again',
);
expect(createFlash).toHaveBeenCalledWith({
message: 'Something went wrong when reordering designs. Please try again',
});
});
});
});

View File

@ -11,7 +11,7 @@ import {
UPDATE_IMAGE_DIFF_NOTE_ERROR,
} from '~/design_management/utils/error_messages';
import design from '../mock_data/design';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import createFlash from '~/flash';
jest.mock('~/flash.js');
@ -35,7 +35,7 @@ describe('Design Management cache update', () => {
expect(createFlash).not.toHaveBeenCalled();
expect(() => subject(mockStore, { errors: mockErrors }, {}, ...extraArgs)).toThrow();
expect(createFlash).toHaveBeenCalledTimes(1);
expect(createFlash).toHaveBeenCalledWith(errorMessage);
expect(createFlash).toHaveBeenCalledWith({ message: errorMessage });
});
});
});

View File

@ -26,8 +26,51 @@ RSpec.describe SearchController, '(JavaScript fixtures)', type: :controller do
context 'search within a project' do
let(:namespace) { create(:namespace, name: 'frontend-fixtures') }
let(:project) { create(:project, :public, :repository, namespace: namespace, path: 'search-project') }
let(:blobs) do
Kaminari.paginate_array([
Gitlab::Search::FoundBlob.new(
path: 'CHANGELOG',
basename: 'CHANGELOG',
ref: 'master',
data: "hello\nworld\nfoo\nSend # this is the highligh\nbaz\nboo\nbat",
project: project,
project_id: project.id,
startline: 2),
Gitlab::Search::FoundBlob.new(
path: 'CONTRIBUTING',
basename: 'CONTRIBUTING',
ref: 'master',
data: "hello\nworld\nfoo\nSend # this is the highligh\nbaz\nboo\nbat",
project: project,
project_id: project.id,
startline: 2),
Gitlab::Search::FoundBlob.new(
path: 'README',
basename: 'README',
ref: 'master',
data: "foo\nSend # this is the highlight\nbaz\nboo\nbat",
project: project,
project_id: project.id,
startline: 2),
Gitlab::Search::FoundBlob.new(
path: 'test',
basename: 'test',
ref: 'master',
data: "foo\nSend # this is the highlight\nbaz\nboo\nbat",
project: project,
project_id: project.id,
startline: 2)
],
total_count: 4,
limit: 4,
offset: 0)
end
it 'search/blob_search_result.html' do
expect_next_instance_of(SearchService) do |search_service|
expect(search_service).to receive(:search_objects).and_return(blobs)
end
get :show, params: {
search: 'Send',
project_id: project.id,

View File

@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
import { GlDeprecatedDropdown, GlDeprecatedDropdownItem } from '@gitlab/ui';
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import DropdownField from '~/monitoring/components/variables/dropdown_field.vue';
describe('Custom variable component', () => {
@ -23,8 +23,8 @@ describe('Custom variable component', () => {
});
};
const findDropdown = () => wrapper.find(GlDeprecatedDropdown);
const findDropdownItems = () => wrapper.findAll(GlDeprecatedDropdownItem);
const findDropdown = () => wrapper.find(GlDropdown);
const findDropdownItems = () => wrapper.findAll(GlDropdownItem);
it('renders dropdown element when all necessary props are passed', () => {
createShallowWrapper();

View File

@ -92,15 +92,14 @@ describe('note_awards_list component', () => {
}).$mount();
};
const findTooltip = () =>
vm.$el.querySelector('[data-original-title]').getAttribute('data-original-title');
const findTooltip = () => vm.$el.querySelector('[title]').getAttribute('title');
it('should only escape & and " characters', () => {
awardsMock = [...new Array(1)].map(createAwardEmoji);
mountComponent();
const escapedName = awardsMock[0].user.name.replace(/&/g, '&amp;').replace(/"/g, '&quot;');
expect(vm.$el.querySelector('[data-original-title]').outerHTML).toContain(escapedName);
expect(vm.$el.querySelector('[title]').outerHTML).toContain(escapedName);
});
it('should not escape special HTML characters twice when only 1 person awarded', () => {

View File

@ -1,8 +1,8 @@
import setHighlightClass from '~/pages/search/show/highlight_blob_search_result';
import setHighlightClass from '~/search/highlight_blob_search_result';
const fixture = 'search/blob_search_result.html';
describe('pages/search/show/highlight_blob_search_result', () => {
describe('search/highlight_blob_search_result', () => {
preloadFixtures(fixture);
beforeEach(() => loadFixtures(fixture));
@ -10,6 +10,6 @@ describe('pages/search/show/highlight_blob_search_result', () => {
it('highlights lines with search term occurrence', () => {
setHighlightClass();
expect(document.querySelectorAll('.blob-result .hll').length).toBe(11);
expect(document.querySelectorAll('.blob-result .hll').length).toBe(4);
});
});

View File

@ -1,10 +1,10 @@
import $ from 'jquery';
import setHighlightClass from 'ee_else_ce/search/highlight_blob_search_result';
import Api from '~/api';
import Search from '~/pages/search/show/search';
import setHighlightClass from '~/pages/search/show/highlight_blob_search_result';
jest.mock('~/api');
jest.mock('~/pages/search/show/highlight_blob_search_result');
jest.mock('ee_else_ce/search/highlight_blob_search_result');
describe('Search', () => {
const fixturePath = 'search/show.html';

View File

@ -6,10 +6,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
>
<button
class="btn award-control"
data-boundary="viewport"
data-original-title="Ada, Leonardo, and Marie"
data-testid="award-button"
title=""
title="Ada, Leonardo, and Marie"
type="button"
>
<span
@ -32,10 +30,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</button>
<button
class="btn award-control active"
data-boundary="viewport"
data-original-title="You, Ada, and Marie"
data-testid="award-button"
title=""
title="You, Ada, and Marie"
type="button"
>
<span
@ -58,10 +54,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</button>
<button
class="btn award-control"
data-boundary="viewport"
data-original-title="Ada and Jane"
data-testid="award-button"
title=""
title="Ada and Jane"
type="button"
>
<span
@ -84,10 +78,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</button>
<button
class="btn award-control active"
data-boundary="viewport"
data-original-title="You, Ada, Jane, and Leonardo"
data-testid="award-button"
title=""
title="You, Ada, Jane, and Leonardo"
type="button"
>
<span
@ -110,10 +102,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</button>
<button
class="btn award-control active"
data-boundary="viewport"
data-original-title="You"
data-testid="award-button"
title=""
title="You"
type="button"
>
<span
@ -136,10 +126,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</button>
<button
class="btn award-control"
data-boundary="viewport"
data-original-title="Marie"
data-testid="award-button"
title=""
title="Marie"
type="button"
>
<span
@ -162,10 +150,8 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
</button>
<button
class="btn award-control active"
data-boundary="viewport"
data-original-title="You"
data-testid="award-button"
title=""
title="You"
type="button"
>
<span
@ -193,9 +179,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
<button
aria-label="Add reaction"
class="award-control btn js-add-award js-test-add-button-class"
data-boundary="viewport"
data-original-title="Add reaction"
title=""
title="Add reaction"
type="button"
>
<span

View File

@ -62,7 +62,7 @@ describe('vue_shared/components/awards_list', () => {
findAwardButtons().wrappers.map(x => {
return {
classes: x.classes(),
title: x.attributes('data-original-title'),
title: x.attributes('title'),
html: x.find('[data-testid="award-html"]').element.innerHTML,
count: Number(x.find('.js-counter').text()),
};

View File

@ -525,4 +525,36 @@ RSpec.describe Gitlab::BitbucketServerImport::Importer do
expect { subject.execute }.to change { MergeRequest.count }.by(1)
end
end
context "lfs files" do
before do
allow(project).to receive(:lfs_enabled?).and_return(true)
allow(subject).to receive(:import_repository)
allow(subject).to receive(:import_pull_requests)
end
it "downloads lfs objects if lfs_enabled is enabled for project" do
expect_next_instance_of(Projects::LfsPointers::LfsImportService) do |lfs_import_service|
expect(lfs_import_service).to receive(:execute).and_return(status: :success)
end
subject.execute
end
it "adds the error message when the lfs download fails" do
allow_next_instance_of(Projects::LfsPointers::LfsImportService) do |lfs_import_service|
expect(lfs_import_service).to receive(:execute).and_return(status: :error, message: "LFS server not reachable")
end
subject.execute
expect(project.import_state.reload.last_error).to eq(Gitlab::Json.dump({
message: "The remote data could not be fully imported.",
errors: [{
type: "lfs_objects",
errors: "The Lfs import process failed. LFS server not reachable"
}]
}))
end
end
end

View File

@ -48,26 +48,10 @@ RSpec.describe Analytics::InstanceStatistics::Measurement, type: :model do
end
describe '#measurement_identifier_values' do
let(:expected_count) { Analytics::InstanceStatistics::Measurement.identifiers.size }
subject { described_class.measurement_identifier_values.count }
context 'when the `store_ci_pipeline_counts_by_status` feature flag is off' do
let(:expected_count) { Analytics::InstanceStatistics::Measurement.identifiers.size - Analytics::InstanceStatistics::Measurement::EXPERIMENTAL_IDENTIFIERS.size }
before do
stub_feature_flags(store_ci_pipeline_counts_by_status: false)
end
it { is_expected.to eq(expected_count) }
end
context 'when the `store_ci_pipeline_counts_by_status` feature flag is on' do
let(:expected_count) { Analytics::InstanceStatistics::Measurement.identifiers.size }
before do
stub_feature_flags(store_ci_pipeline_counts_by_status: true)
end
it { is_expected.to eq(expected_count) }
end
it { is_expected.to eq(expected_count) }
end
end

View File

@ -135,11 +135,31 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
context 'when data_store is fog' do
let(:data_store) { :fog }
before do
build_trace_chunk.send(:unsafe_set_data!, +'Sample data in fog')
context 'when legacy Fog is enabled' do
before do
stub_feature_flags(ci_trace_new_fog_store: false)
build_trace_chunk.send(:unsafe_set_data!, +'Sample data in fog')
end
it { is_expected.to eq('Sample data in fog') }
it 'returns a LegacyFog store' do
expect(described_class.get_store_class(data_store)).to be_a(Ci::BuildTraceChunks::LegacyFog)
end
end
it { is_expected.to eq('Sample data in fog') }
context 'when new Fog is enabled' do
before do
stub_feature_flags(ci_trace_new_fog_store: true)
build_trace_chunk.send(:unsafe_set_data!, +'Sample data in fog')
end
it { is_expected.to eq('Sample data in fog') }
it 'returns a new Fog store' do
expect(described_class.get_store_class(data_store)).to be_a(Ci::BuildTraceChunks::Fog)
end
end
end
end

View File

@ -4,8 +4,12 @@ require 'spec_helper'
RSpec.describe Ci::BuildTraceChunks::Fog do
let(:data_store) { described_class.new }
let(:bucket) { 'artifacts' }
let(:connection_params) { Gitlab.config.artifacts.object_store.connection.symbolize_keys }
let(:connection) { ::Fog::Storage.new(connection_params) }
before do
stub_object_storage(connection_params: connection_params, remote_directory: bucket)
stub_artifacts_object_storage
end
@ -148,17 +152,17 @@ RSpec.describe Ci::BuildTraceChunks::Fog do
end
it 'deletes multiple data' do
::Fog::Storage.new(JobArtifactUploader.object_store_credentials).tap do |connection|
expect(connection.get_object('artifacts', "tmp/builds/#{build.id}/chunks/0.log")[:body]).to be_present
expect(connection.get_object('artifacts', "tmp/builds/#{build.id}/chunks/1.log")[:body]).to be_present
end
files = connection.directories.new(key: bucket).files
expect(files.count).to eq(2)
expect(files[0].body).to be_present
expect(files[1].body).to be_present
subject
::Fog::Storage.new(JobArtifactUploader.object_store_credentials).tap do |connection|
expect { connection.get_object('artifacts', "tmp/builds/#{build.id}/chunks/0.log")[:body] }.to raise_error(Excon::Error::NotFound)
expect { connection.get_object('artifacts', "tmp/builds/#{build.id}/chunks/1.log")[:body] }.to raise_error(Excon::Error::NotFound)
end
files.reload
expect(files.count).to eq(0)
end
end
end

View File

@ -0,0 +1,164 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::BuildTraceChunks::LegacyFog do
let(:data_store) { described_class.new }
before do
stub_artifacts_object_storage
end
describe '#available?' do
subject { data_store.available? }
context 'when object storage is enabled' do
it { is_expected.to be_truthy }
end
context 'when object storage is disabled' do
before do
stub_artifacts_object_storage(enabled: false)
end
it { is_expected.to be_falsy }
end
end
describe '#data' do
subject { data_store.data(model) }
context 'when data exists' do
let(:model) { create(:ci_build_trace_chunk, :fog_with_data, initial_data: 'sample data in fog') }
it 'returns the data' do
is_expected.to eq('sample data in fog')
end
end
context 'when data does not exist' do
let(:model) { create(:ci_build_trace_chunk, :fog_without_data) }
it 'returns nil' do
expect(data_store.data(model)).to be_nil
end
end
end
describe '#set_data' do
let(:new_data) { 'abc123' }
context 'when data exists' do
let(:model) { create(:ci_build_trace_chunk, :fog_with_data, initial_data: 'sample data in fog') }
it 'overwrites data' do
expect(data_store.data(model)).to eq('sample data in fog')
data_store.set_data(model, new_data)
expect(data_store.data(model)).to eq new_data
end
end
context 'when data does not exist' do
let(:model) { create(:ci_build_trace_chunk, :fog_without_data) }
it 'sets new data' do
expect(data_store.data(model)).to be_nil
data_store.set_data(model, new_data)
expect(data_store.data(model)).to eq new_data
end
end
end
describe '#delete_data' do
subject { data_store.delete_data(model) }
context 'when data exists' do
let(:model) { create(:ci_build_trace_chunk, :fog_with_data, initial_data: 'sample data in fog') }
it 'deletes data' do
expect(data_store.data(model)).to eq('sample data in fog')
subject
expect(data_store.data(model)).to be_nil
end
end
context 'when data does not exist' do
let(:model) { create(:ci_build_trace_chunk, :fog_without_data) }
it 'does nothing' do
expect(data_store.data(model)).to be_nil
subject
expect(data_store.data(model)).to be_nil
end
end
end
describe '#size' do
context 'when data exists' do
let(:model) { create(:ci_build_trace_chunk, :fog_with_data, initial_data: 'üabcd') }
it 'returns data bytesize correctly' do
expect(data_store.size(model)).to eq 6
end
end
context 'when data does not exist' do
let(:model) { create(:ci_build_trace_chunk, :fog_without_data) }
it 'returns zero' do
expect(data_store.size(model)).to be_zero
end
end
end
describe '#keys' do
subject { data_store.keys(relation) }
let(:build) { create(:ci_build) }
let(:relation) { build.trace_chunks }
before do
create(:ci_build_trace_chunk, :fog_with_data, chunk_index: 0, build: build)
create(:ci_build_trace_chunk, :fog_with_data, chunk_index: 1, build: build)
end
it 'returns keys' do
is_expected.to eq([[build.id, 0], [build.id, 1]])
end
end
describe '#delete_keys' do
subject { data_store.delete_keys(keys) }
let(:build) { create(:ci_build) }
let(:relation) { build.trace_chunks }
let(:keys) { data_store.keys(relation) }
before do
create(:ci_build_trace_chunk, :fog_with_data, chunk_index: 0, build: build)
create(:ci_build_trace_chunk, :fog_with_data, chunk_index: 1, build: build)
end
it 'deletes multiple data' do
::Fog::Storage.new(JobArtifactUploader.object_store_credentials).tap do |connection|
expect(connection.get_object('artifacts', "tmp/builds/#{build.id}/chunks/0.log")[:body]).to be_present
expect(connection.get_object('artifacts', "tmp/builds/#{build.id}/chunks/1.log")[:body]).to be_present
end
subject
::Fog::Storage.new(JobArtifactUploader.object_store_credentials).tap do |connection|
expect { connection.get_object('artifacts', "tmp/builds/#{build.id}/chunks/0.log")[:body] }.to raise_error(Excon::Error::NotFound)
expect { connection.get_object('artifacts', "tmp/builds/#{build.id}/chunks/1.log")[:body] }.to raise_error(Excon::Error::NotFound)
end
end
end
end

View File

@ -843,5 +843,24 @@ RSpec.describe Service do
service.log_error(test_message, additional_argument: 'some argument')
end
context 'when project is nil' do
let(:project) { nil }
let(:arguments) do
{
service_class: service.class.name,
project_path: nil,
project_id: nil,
message: test_message,
additional_argument: 'some argument'
}
end
it 'logs info messages using json logger' do
expect(Gitlab::JsonLogger).to receive(:info).with(arguments)
service.log_info(test_message, additional_argument: 'some argument')
end
end
end
end

View File

@ -14,7 +14,9 @@ RSpec.describe 'Robots.txt Requests', :aggregate_failures do
it 'allows the requests' do
requests = [
'/users/sign_in',
'/namespace/subnamespace/design.gitlab.com'
'/namespace/subnamespace/design.gitlab.com',
'/users/foo/snippets',
'/users/foo/snippets/1'
]
requests.each do |request|

View File

@ -32,16 +32,8 @@ module SnowplowHelpers
# end
# end
def expect_snowplow_event(category:, action:, **kwargs)
# This check will no longer be needed with Ruby 2.7 which
# would not pass any arguments when using **kwargs.
# https://gitlab.com/gitlab-org/gitlab/-/issues/263430
if kwargs.present?
expect(Gitlab::Tracking).to have_received(:event) # rubocop:disable RSpec/ExpectGitlabTracking
.with(category, action, **kwargs).at_least(:once)
else
expect(Gitlab::Tracking).to have_received(:event) # rubocop:disable RSpec/ExpectGitlabTracking
.with(category, action).at_least(:once)
end
expect(Gitlab::Tracking).to have_received(:event) # rubocop:disable RSpec/ExpectGitlabTracking
.with(category, action, **kwargs).at_least(:once)
end
# Asserts that no call to `Gitlab::Tracking#event` was made.