Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
77d49e6a73
commit
3e49ae159a
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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.")
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix exception when saving Jira integration info for an instance
|
||||
merge_request: 45718
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Allow user snippets to be indexed by search crawlers
|
||||
merge_request: 45793
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Replace-GlDeprecatedDropdown-with-GlDropdown-in-app/assets/javascripts/monitoring
|
||||
merge_request: 41422
|
||||
author: nuwe1
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Download LFS files when importing from Bitbucket Server
|
||||
merge_request: 45908
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Migrate tooltip in app/assets/javascripts/vue_shared/components/awards_list.vue
|
||||
merge_request: 46171
|
||||
author:
|
||||
type: other
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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}))'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
```
|
||||
|
|
|
@ -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/).
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 Group’s 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)
|
||||
```
|
|
@ -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. |
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -34,6 +34,7 @@ code_quality:
|
|||
CODECLIMATE_DEBUG \
|
||||
CODECLIMATE_DEV \
|
||||
REPORT_STDOUT \
|
||||
REPORT_FORMAT \
|
||||
ENGINE_MEMORY_LIMIT_BYTES \
|
||||
) \
|
||||
--volume "$PWD":/code \
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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 });
|
||||
});
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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, '&').replace(/"/g, '"');
|
||||
|
||||
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', () => {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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';
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()),
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue