Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
4f33294e27
commit
a79324ad1f
|
@ -147,6 +147,8 @@
|
|||
/ee/spec/javascripts/ @gitlab-org/maintainers/frontend
|
||||
/spec/frontend/ @gitlab-org/maintainers/frontend
|
||||
/ee/spec/frontend/ @gitlab-org/maintainers/frontend
|
||||
/spec/frontend_integration/ @gitlab-org/maintainers/frontend
|
||||
/ee/spec/frontend_integration/ @gitlab-org/maintainers/frontend
|
||||
|
||||
[Database]
|
||||
/db/ @gitlab-org/maintainers/database
|
||||
|
|
|
@ -82,7 +82,6 @@ export default class FileTemplateMediator {
|
|||
|
||||
initPageEvents() {
|
||||
this.listenForFilenameInput();
|
||||
this.prepFileContentForSubmit();
|
||||
this.listenForPreviewMode();
|
||||
}
|
||||
|
||||
|
@ -92,12 +91,6 @@ export default class FileTemplateMediator {
|
|||
});
|
||||
}
|
||||
|
||||
prepFileContentForSubmit() {
|
||||
this.$commitForm.submit(() => {
|
||||
this.$fileContent.val(this.editor.getValue());
|
||||
});
|
||||
}
|
||||
|
||||
listenForPreviewMode() {
|
||||
this.$navLinks.on('click', 'a', e => {
|
||||
const urlPieces = e.target.href.split('#');
|
||||
|
|
|
@ -6,6 +6,7 @@ import TemplateSelectorMediator from '../blob/file_template_mediator';
|
|||
import { addEditorMarkdownListeners } from '~/lib/utils/text_markdown';
|
||||
import EditorLite from '~/editor/editor_lite';
|
||||
import { FileTemplateExtension } from '~/editor/editor_file_template_ext';
|
||||
import { insertFinalNewline } from '~/lib/utils/text_utility';
|
||||
|
||||
export default class EditBlob {
|
||||
// The options object has:
|
||||
|
@ -49,7 +50,7 @@ export default class EditBlob {
|
|||
});
|
||||
|
||||
form.addEventListener('submit', () => {
|
||||
fileContentEl.value = this.editor.getValue();
|
||||
fileContentEl.value = insertFinalNewline(this.editor.getValue());
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -191,7 +191,13 @@ export const fetchDiffFilesMeta = ({ commit, state }) => {
|
|||
commit(types.SET_MERGE_REQUEST_DIFFS, data.merge_request_diffs || []);
|
||||
commit(types.SET_DIFF_METADATA, strippedData);
|
||||
|
||||
worker.postMessage(prepareDiffData(data, state.diffFiles));
|
||||
worker.postMessage(
|
||||
prepareDiffData({
|
||||
diff: data,
|
||||
priorFiles: state.diffFiles,
|
||||
meta: true,
|
||||
}),
|
||||
);
|
||||
|
||||
return data;
|
||||
})
|
||||
|
|
|
@ -73,7 +73,10 @@ export default {
|
|||
},
|
||||
|
||||
[types.SET_DIFF_DATA_BATCH](state, data) {
|
||||
const files = prepareDiffData(data, state.diffFiles);
|
||||
const files = prepareDiffData({
|
||||
diff: data,
|
||||
priorFiles: state.diffFiles,
|
||||
});
|
||||
|
||||
Object.assign(state, {
|
||||
...convertObjectPropsToCamelCase(data),
|
||||
|
@ -143,7 +146,7 @@ export default {
|
|||
},
|
||||
|
||||
[types.ADD_COLLAPSED_DIFFS](state, { file, data }) {
|
||||
const files = prepareDiffData(data);
|
||||
const files = prepareDiffData({ diff: data });
|
||||
const [newFileData] = files.filter(f => f.file_hash === file.file_hash);
|
||||
const selectedFile = state.diffFiles.find(f => f.file_hash === file.file_hash);
|
||||
Object.assign(selectedFile, { ...newFileData });
|
||||
|
|
|
@ -386,9 +386,9 @@ function deduplicateFilesList(files) {
|
|||
return Object.values(dedupedFiles);
|
||||
}
|
||||
|
||||
export function prepareDiffData(diff, priorFiles = []) {
|
||||
export function prepareDiffData({ diff, priorFiles = [], meta = false }) {
|
||||
const cleanedFiles = (diff.diff_files || [])
|
||||
.map((file, index, allFiles) => prepareRawDiffFile({ file, allFiles }))
|
||||
.map((file, index, allFiles) => prepareRawDiffFile({ file, allFiles, meta }))
|
||||
.map(ensureBasicDiffFileLines)
|
||||
.map(prepareDiffFileLines)
|
||||
.map(finalizeDiffFile);
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
DIFF_FILE_MANUAL_COLLAPSE,
|
||||
DIFF_FILE_AUTOMATIC_COLLAPSE,
|
||||
} from '../constants';
|
||||
import { uuids } from './uuids';
|
||||
|
||||
function fileSymlinkInformation(file, fileList) {
|
||||
const duplicates = fileList.filter(iteratedFile => iteratedFile.file_hash === file.file_hash);
|
||||
|
@ -32,16 +33,29 @@ function collapsed(file) {
|
|||
};
|
||||
}
|
||||
|
||||
export function prepareRawDiffFile({ file, allFiles }) {
|
||||
Object.assign(file, {
|
||||
function identifier(file) {
|
||||
return uuids({
|
||||
seeds: [file.file_identifier_hash, file.content_sha],
|
||||
})[0];
|
||||
}
|
||||
|
||||
export function prepareRawDiffFile({ file, allFiles, meta = false }) {
|
||||
const additionalProperties = {
|
||||
brokenSymlink: fileSymlinkInformation(file, allFiles),
|
||||
viewer: {
|
||||
...file.viewer,
|
||||
...collapsed(file),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return file;
|
||||
// It's possible, but not confirmed, that `content_sha` isn't available sometimes
|
||||
// See: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49506#note_464692057
|
||||
// We don't want duplicate IDs if that's the case, so we just don't assign an ID
|
||||
if (!meta && file.content_sha) {
|
||||
additionalProperties.id = identifier(file);
|
||||
}
|
||||
|
||||
return Object.assign(file, additionalProperties);
|
||||
}
|
||||
|
||||
export function collapsedType(file) {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { editor as monacoEditor, Uri } from 'monaco-editor';
|
||||
import Disposable from './disposable';
|
||||
import eventHub from '../../eventhub';
|
||||
import { trimTrailingWhitespace, insertFinalNewline } from '../../utils';
|
||||
import { trimTrailingWhitespace } from '../../utils';
|
||||
import { insertFinalNewline } from '~/lib/utils/text_utility';
|
||||
import { defaultModelOptions } from '../editor_options';
|
||||
|
||||
export default class Model {
|
||||
|
|
|
@ -97,10 +97,6 @@ export function trimTrailingWhitespace(content) {
|
|||
return content.replace(/[^\S\r\n]+$/gm, '');
|
||||
}
|
||||
|
||||
export function insertFinalNewline(content, eol = '\n') {
|
||||
return content.slice(-eol.length) !== eol ? `${content}${eol}` : content;
|
||||
}
|
||||
|
||||
export function getPathParents(path, maxDepth = Infinity) {
|
||||
const pathComponents = path.split('/');
|
||||
const paths = [];
|
||||
|
|
|
@ -411,3 +411,13 @@ export const hasContent = obj => isString(obj) && obj.trim() !== '';
|
|||
export const isValidSha1Hash = str => {
|
||||
return /^[0-9a-f]{5,40}$/.test(str);
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a final newline to the content if it doesn't already exist
|
||||
*
|
||||
* @param {*} content Content
|
||||
* @param {*} endOfLine Type of newline: CRLF='\r\n', LF='\n', CR='\r'
|
||||
*/
|
||||
export function insertFinalNewline(content, endOfLine = '\n') {
|
||||
return content.slice(-endOfLine.length) !== endOfLine ? `${content}${endOfLine}` : content;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
|
|||
before_action :authorize_admin_group_member!, except: admin_not_required_endpoints
|
||||
|
||||
before_action do
|
||||
push_frontend_feature_flag(:group_members_filtered_search, @group)
|
||||
push_frontend_feature_flag(:group_members_filtered_search, @group, default_enabled: true)
|
||||
end
|
||||
|
||||
skip_before_action :check_two_factor_requirement, only: :leave
|
||||
|
|
|
@ -57,7 +57,7 @@ class Import::BulkImportsController < ApplicationController
|
|||
end
|
||||
|
||||
def create_params
|
||||
params.permit(:bulk_import, [*bulk_import_params])
|
||||
params.permit(bulk_import: bulk_import_params)[:bulk_import]
|
||||
end
|
||||
|
||||
def bulk_import_params
|
||||
|
@ -127,7 +127,7 @@ class Import::BulkImportsController < ApplicationController
|
|||
def credentials
|
||||
{
|
||||
url: session[url_key],
|
||||
access_token: [access_token_key]
|
||||
access_token: session[access_token_key]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -745,31 +745,7 @@ class MergeRequestDiff < ApplicationRecord
|
|||
def sort_diffs(diffs)
|
||||
return diffs unless sort_diffs?
|
||||
|
||||
diffs.sort do |a, b|
|
||||
compare_path_parts(path_parts(a), path_parts(b))
|
||||
end
|
||||
end
|
||||
|
||||
def path_parts(diff)
|
||||
(diff.new_path.presence || diff.old_path).split(::File::SEPARATOR)
|
||||
end
|
||||
|
||||
# Used for sorting the file paths by:
|
||||
# 1. Directory name
|
||||
# 2. Depth
|
||||
# 3. File name
|
||||
def compare_path_parts(a_parts, b_parts)
|
||||
a_part = a_parts.shift
|
||||
b_part = b_parts.shift
|
||||
|
||||
return 1 if a_parts.size < b_parts.size && a_parts.empty?
|
||||
return -1 if a_parts.size > b_parts.size && b_parts.empty?
|
||||
|
||||
comparison = a_part <=> b_part
|
||||
|
||||
return comparison unless comparison == 0
|
||||
|
||||
compare_path_parts(a_parts, b_parts)
|
||||
Gitlab::Diff::FileCollectionSorter.new(diffs).sort
|
||||
end
|
||||
|
||||
def sort_diffs?
|
||||
|
|
|
@ -5,6 +5,6 @@ class RawUsageData < ApplicationRecord
|
|||
validates :recorded_at, presence: true, uniqueness: true
|
||||
|
||||
def update_sent_at!
|
||||
self.update_column(:sent_at, Time.current) if Feature.enabled?(:save_raw_usage_data)
|
||||
self.update_column(:sent_at, Time.current)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
|
||||
class DiffsMetadataEntity < DiffsEntity
|
||||
unexpose :diff_files
|
||||
expose :raw_diff_files, as: :diff_files, using: DiffFileMetadataEntity
|
||||
expose :diff_files, using: DiffFileMetadataEntity do |diffs, _|
|
||||
diffs.raw_diff_files(sorted: true)
|
||||
end
|
||||
|
||||
expose :conflict_resolution_path do |_, options|
|
||||
presenter(options[:merge_request]).conflict_resolution_path
|
||||
|
|
|
@ -12,4 +12,8 @@ class Import::BulkImportEntity < Grape::Entity
|
|||
expose :full_path do |entity|
|
||||
entity['full_path']
|
||||
end
|
||||
|
||||
expose :web_url do |entity|
|
||||
entity['web_url']
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,7 +13,7 @@ class PaginatedDiffEntity < Grape::Entity
|
|||
submodule_links = Gitlab::SubmoduleLinks.new(merge_request.project.repository)
|
||||
|
||||
DiffFileEntity.represent(
|
||||
diffs.diff_files,
|
||||
diffs.diff_files(sorted: true),
|
||||
options.merge(
|
||||
submodule_links: submodule_links,
|
||||
code_navigation_path: code_navigation_path(diffs),
|
||||
|
|
|
@ -43,8 +43,6 @@ class SubmitUsagePingService
|
|||
private
|
||||
|
||||
def save_raw_usage_data(usage_data)
|
||||
return unless Feature.enabled?(:save_raw_usage_data)
|
||||
|
||||
RawUsageData.safe_find_or_create_by(recorded_at: usage_data[:recorded_at]) do |record|
|
||||
record.payload = usage_data
|
||||
end
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
- show_access_requests = can_manage_members && @requesters.exists?
|
||||
- invited_active = params[:search_invited].present? || params[:invited_members_page].present?
|
||||
- vue_members_list_enabled = Feature.enabled?(:vue_group_members_list, @group, default_enabled: true)
|
||||
- filtered_search_enabled = Feature.enabled?(:group_members_filtered_search, @group)
|
||||
- filtered_search_enabled = Feature.enabled?(:group_members_filtered_search, @group, default_enabled: true)
|
||||
- current_user_is_group_owner = @group && @group.has_owner?(current_user)
|
||||
|
||||
- form_item_label_css_class = 'label-bold gl-mr-2 gl-mb-0 gl-py-2 align-self-md-center'
|
||||
|
|
|
@ -27,9 +27,6 @@
|
|||
= sprite_icon("rocket", size: 12)
|
||||
= _("Release")
|
||||
= link_to release.name, project_releases_path(@project, anchor: release.tag), class: 'gl-text-blue-600!'
|
||||
- if release.description.present?
|
||||
.md.gl-mt-3
|
||||
= markdown_field(release, :description)
|
||||
|
||||
.row-fixed-content.controls.flex-row
|
||||
- if tag.has_signature?
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update deprecated button on pipeline security table
|
||||
merge_request: 49620
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Save usage ping payload in raw_usage_data table
|
||||
merge_request: 49559
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Sort commit/compare diff files directory first
|
||||
merge_request: 49136
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add final newline on submit in blob editor
|
||||
merge_request: 49681
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Convert group member filter dropdowns to filtered search bar
|
||||
merge_request: 49505
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove release notes from Tags page
|
||||
merge_request: 49979
|
||||
author:
|
||||
type: removed
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/289911
|
|||
milestone: '13.7'
|
||||
type: development
|
||||
group: group::access
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: save_raw_usage_data
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38457
|
||||
rollout_issue_url:
|
||||
milestone: '13.3'
|
||||
type: development
|
||||
group: group::product analytics
|
||||
default_enabled: false
|
|
@ -4121,6 +4121,56 @@ type CreateClusterAgentPayload {
|
|||
errors: [String!]!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated input type of CreateComplianceFramework
|
||||
"""
|
||||
input CreateComplianceFrameworkInput {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
Color to represent the compliance framework as a hexadecimal value. e.g. #ABC123.
|
||||
"""
|
||||
color: String!
|
||||
|
||||
"""
|
||||
Description of the compliance framework.
|
||||
"""
|
||||
description: String!
|
||||
|
||||
"""
|
||||
Name of the compliance framework.
|
||||
"""
|
||||
name: String!
|
||||
|
||||
"""
|
||||
Full path of the namespace to add the compliance framework to.
|
||||
"""
|
||||
namespacePath: ID!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated return type of CreateComplianceFramework
|
||||
"""
|
||||
type CreateComplianceFrameworkPayload {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
Errors encountered during execution of the mutation.
|
||||
"""
|
||||
errors: [String!]!
|
||||
|
||||
"""
|
||||
The created compliance framework.
|
||||
"""
|
||||
framework: ComplianceFramework
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated input type of CreateCustomEmoji
|
||||
"""
|
||||
|
@ -14545,6 +14595,7 @@ type Mutation {
|
|||
createBoard(input: CreateBoardInput!): CreateBoardPayload
|
||||
createBranch(input: CreateBranchInput!): CreateBranchPayload
|
||||
createClusterAgent(input: CreateClusterAgentInput!): CreateClusterAgentPayload
|
||||
createComplianceFramework(input: CreateComplianceFrameworkInput!): CreateComplianceFrameworkPayload
|
||||
|
||||
"""
|
||||
Available only when feature flag `custom_emoji` is enabled.
|
||||
|
|
|
@ -11320,6 +11320,150 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "CreateComplianceFrameworkInput",
|
||||
"description": "Autogenerated input type of CreateComplianceFramework",
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "namespacePath",
|
||||
"description": "Full path of the namespace to add the compliance framework to.",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"description": "Name of the compliance framework.",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"description": "Description of the compliance framework.",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "color",
|
||||
"description": "Color to represent the compliance framework as a hexadecimal value. e.g. #ABC123.",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "clientMutationId",
|
||||
"description": "A unique identifier for the client performing the mutation.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "CreateComplianceFrameworkPayload",
|
||||
"description": "Autogenerated return type of CreateComplianceFramework",
|
||||
"fields": [
|
||||
{
|
||||
"name": "clientMutationId",
|
||||
"description": "A unique identifier for the client performing the mutation.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "errors",
|
||||
"description": "Errors encountered during execution of the mutation.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "framework",
|
||||
"description": "The created compliance framework.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "ComplianceFramework",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "CreateCustomEmojiInput",
|
||||
|
@ -40796,6 +40940,33 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "createComplianceFramework",
|
||||
"description": null,
|
||||
"args": [
|
||||
{
|
||||
"name": "input",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "CreateComplianceFrameworkInput",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "CreateComplianceFrameworkPayload",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "createCustomEmoji",
|
||||
"description": " Available only when feature flag `custom_emoji` is enabled.",
|
||||
|
|
|
@ -693,6 +693,16 @@ Autogenerated return type of CreateClusterAgent.
|
|||
| `clusterAgent` | ClusterAgent | Cluster agent created after mutation |
|
||||
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
|
||||
|
||||
### CreateComplianceFrameworkPayload
|
||||
|
||||
Autogenerated return type of CreateComplianceFramework.
|
||||
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
|
||||
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
|
||||
| `framework` | ComplianceFramework | The created compliance framework. |
|
||||
|
||||
### CreateCustomEmojiPayload
|
||||
|
||||
Autogenerated return type of CreateCustomEmoji.
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 28 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.2 KiB |
|
@ -15,7 +15,9 @@ source branch into a target branch. By default, the CI pipeline runs jobs
|
|||
against the source branch.
|
||||
|
||||
With *pipelines for merged results*, the pipeline runs as if the changes from
|
||||
the source branch have already been merged into the target branch.
|
||||
the source branch have already been merged into the target branch. The commit shown for the pipeline does not exist on the source or target branches but represents the combined target and source branches.
|
||||
|
||||
![Merge request widget for merged results pipeline](img/merged_result_pipeline.png)
|
||||
|
||||
If the pipeline fails due to a problem in the target branch, you can wait until the
|
||||
target is fixed and re-run the pipeline.
|
||||
|
|
|
@ -65,6 +65,39 @@ execution time and the error output.
|
|||
|
||||
![Test Reports Widget](img/junit_test_report.png)
|
||||
|
||||
### Number of recent failures
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241759) in GitLab 13.7.
|
||||
> - 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-the-number-of-recent-failures). **(CORE ONLY)**
|
||||
|
||||
WARNING:
|
||||
This feature might not be available to you. Check the **version history** note above for details.
|
||||
|
||||
If a test failed in the project's default branch in the last 14 days, a message like
|
||||
`Failed {n} time(s) in {default_branch} in the last 14 days` is displayed for that test.
|
||||
|
||||
#### Enable or disable the number of recent failures **(CORE ONLY)**
|
||||
|
||||
Displaying the number of failures in the last 14 days 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(:test_failure_history)
|
||||
```
|
||||
|
||||
To disable it:
|
||||
|
||||
```ruby
|
||||
Feature.disable(:test_failure_history)
|
||||
```
|
||||
|
||||
## How to set it up
|
||||
|
||||
To enable the Unit test reports in merge requests, you need to add
|
||||
|
|
|
@ -654,6 +654,17 @@ When the docs are generated, the output is:
|
|||
|
||||
To stop the command, press <kbd>Control</kbd>+<kbd>C</kbd>.
|
||||
|
||||
### Spaces between words
|
||||
|
||||
Use only standard spaces between words. The search engine for the documentation
|
||||
website doesn't split words separated with
|
||||
[non-breaking spaces](https://en.wikipedia.org/wiki/Non-breaking_space) when
|
||||
indexing, and fails to create expected individual search terms. Tests that search
|
||||
for certain words separated by regular spaces can't find words separated by
|
||||
non-breaking spaces.
|
||||
|
||||
Tested in [`lint-doc.sh`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/scripts/lint-doc.sh).
|
||||
|
||||
## Lists
|
||||
|
||||
- Always start list items with a capital letter, unless they're parameters or
|
||||
|
|
|
@ -74,7 +74,7 @@ which means that the reported licenses might be incomplete or inaccurate.
|
|||
| JavaScript | [Yarn](https://yarnpkg.com/)|[License Finder](https://github.com/pivotal/LicenseFinder)|
|
||||
| Go | go get, gvt, glide, dep, trash, govendor |[License Finder](https://github.com/pivotal/LicenseFinder)|
|
||||
| Erlang | [Rebar](https://www.rebar3.org/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
|
||||
| Objective-C, Swift | [Carthage](https://github.com/Carthage/Carthage) | | [License Finder](https://github.com/pivotal/LicenseFinder) |
|
||||
| Objective-C, Swift | [Carthage](https://github.com/Carthage/Carthage) | [License Finder](https://github.com/pivotal/LicenseFinder) |
|
||||
| Objective-C, Swift | [CocoaPods](https://cocoapods.org/) v0.39 and below |[License Finder](https://github.com/pivotal/LicenseFinder)|
|
||||
| Elixir | [Mix](https://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html) |[License Finder](https://github.com/pivotal/LicenseFinder)|
|
||||
| C++/C | [Conan](https://conan.io/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
|
||||
|
|
|
@ -30,12 +30,16 @@ module Gitlab
|
|||
@diffs ||= diffable.raw_diffs(diff_options)
|
||||
end
|
||||
|
||||
def diff_files
|
||||
raw_diff_files
|
||||
def diff_files(sorted: false)
|
||||
raw_diff_files(sorted: sorted)
|
||||
end
|
||||
|
||||
def raw_diff_files
|
||||
@raw_diff_files ||= diffs.decorate! { |diff| decorate_diff!(diff) }
|
||||
def raw_diff_files(sorted: false)
|
||||
strong_memoize(:"raw_diff_files_#{sorted}") do
|
||||
collection = diffs.decorate! { |diff| decorate_diff!(diff) }
|
||||
collection = sort_diffs(collection) if sorted
|
||||
collection
|
||||
end
|
||||
end
|
||||
|
||||
def diff_file_paths
|
||||
|
@ -111,6 +115,12 @@ module Gitlab
|
|||
fallback_diff_refs: fallback_diff_refs,
|
||||
stats: stats)
|
||||
end
|
||||
|
||||
def sort_diffs(diffs)
|
||||
return diffs unless Feature.enabled?(:sort_diffs, project, default_enabled: false)
|
||||
|
||||
Gitlab::Diff::FileCollectionSorter.new(diffs).sort
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,7 +16,7 @@ module Gitlab
|
|||
fallback_diff_refs: merge_request_diff.fallback_diff_refs)
|
||||
end
|
||||
|
||||
def diff_files
|
||||
def diff_files(sorted: false)
|
||||
strong_memoize(:diff_files) do
|
||||
diff_files = super
|
||||
|
||||
|
@ -26,6 +26,12 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def raw_diff_files(sorted: false)
|
||||
# We force `sorted` to `false` as we don't need to sort the diffs when
|
||||
# dealing with `MergeRequestDiff` since we sort its files on create.
|
||||
super(sorted: false)
|
||||
end
|
||||
|
||||
override :write_cache
|
||||
def write_cache
|
||||
highlight_cache.write_if_empty
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Diff
|
||||
class FileCollectionSorter
|
||||
attr_reader :diffs
|
||||
|
||||
def initialize(diffs)
|
||||
@diffs = diffs
|
||||
end
|
||||
|
||||
def sort
|
||||
diffs.sort do |a, b|
|
||||
compare_path_parts(path_parts(a), path_parts(b))
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def path_parts(diff)
|
||||
(diff.new_path.presence || diff.old_path).split(::File::SEPARATOR)
|
||||
end
|
||||
|
||||
# Used for sorting the file paths by:
|
||||
# 1. Directory name
|
||||
# 2. Depth
|
||||
# 3. File name
|
||||
def compare_path_parts(a_parts, b_parts)
|
||||
a_part = a_parts.shift
|
||||
b_part = b_parts.shift
|
||||
|
||||
return 1 if a_parts.size < b_parts.size && a_parts.empty?
|
||||
return -1 if a_parts.size > b_parts.size && b_parts.empty?
|
||||
|
||||
comparison = a_part <=> b_part
|
||||
|
||||
return comparison unless comparison == 0
|
||||
|
||||
compare_path_parts(a_parts, b_parts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -11805,9 +11805,6 @@ msgstr ""
|
|||
msgid "Feature flag was successfully removed."
|
||||
msgstr ""
|
||||
|
||||
msgid "Feature not available"
|
||||
msgstr ""
|
||||
|
||||
msgid "FeatureFlags|%d user"
|
||||
msgid_plural "FeatureFlags|%d users"
|
||||
msgstr[0] ""
|
||||
|
|
|
@ -6,7 +6,7 @@ module RuboCop
|
|||
class IDType < RuboCop::Cop::Cop
|
||||
MSG = 'Do not use GraphQL::ID_TYPE, use a specific GlobalIDType instead'
|
||||
|
||||
WHITELISTED_ARGUMENTS = %i[iid full_path project_path group_path target_project_path].freeze
|
||||
WHITELISTED_ARGUMENTS = %i[iid full_path project_path group_path target_project_path namespace_path].freeze
|
||||
|
||||
def_node_search :graphql_id_type?, <<~PATTERN
|
||||
(send nil? :argument (_ #does_not_match?) (const (const nil? :GraphQL) :ID_TYPE) ...)
|
||||
|
|
|
@ -18,6 +18,21 @@ then
|
|||
((ERRORCODE++))
|
||||
fi
|
||||
|
||||
# Test for non-standard spaces (NBSP, NNBSP) in documentation.
|
||||
echo '=> Checking for non-standard spaces...'
|
||||
echo
|
||||
grep --extended-regexp --binary-file=without-match --recursive '[ ]' doc/ >/dev/null 2>&1
|
||||
if [ $? -eq 0 ]
|
||||
then
|
||||
echo '✖ ERROR: Non-standard spaces (NBSP, NNBSP) should not be used in documentation.
|
||||
https://docs.gitlab.com/ee/development/documentation/styleguide/index.html#spaces-between-words
|
||||
Replace with standard spaces:' >&2
|
||||
# Find the spaces, then add color codes with sed to highlight each NBSP or NNBSP in the output.
|
||||
grep --extended-regexp --binary-file=without-match --recursive --color=auto '[ ]' doc \
|
||||
| sed -e ''/ /s//`printf "\033[0;101m \033[0m"`/'' -e ''/ /s//`printf "\033[0;101m \033[0m"`/''
|
||||
((ERRORCODE++))
|
||||
fi
|
||||
|
||||
# Ensure that the CHANGELOG.md does not contain duplicate versions
|
||||
DUPLICATE_CHANGELOG_VERSIONS=$(grep --extended-regexp '^## .+' CHANGELOG.md | sed -E 's| \(.+\)||' | sort -r | uniq -d)
|
||||
echo '=> Checking for CHANGELOG.md duplicate entries...'
|
||||
|
|
|
@ -57,8 +57,8 @@ RSpec.describe Import::BulkImportsController do
|
|||
let(:client_response) do
|
||||
double(
|
||||
parsed_response: [
|
||||
{ 'id' => 1, 'full_name' => 'group1', 'full_path' => 'full/path/group1' },
|
||||
{ 'id' => 2, 'full_name' => 'group2', 'full_path' => 'full/path/group2' }
|
||||
{ 'id' => 1, 'full_name' => 'group1', 'full_path' => 'full/path/group1', 'web_url' => 'http://demo.host/full/path/group1' },
|
||||
{ 'id' => 2, 'full_name' => 'group2', 'full_path' => 'full/path/group2', 'web_url' => 'http://demo.host/full/path/group1' }
|
||||
]
|
||||
)
|
||||
end
|
||||
|
@ -132,12 +132,27 @@ RSpec.describe Import::BulkImportsController do
|
|||
end
|
||||
|
||||
describe 'POST create' do
|
||||
let(:instance_url) { "http://fake-intance" }
|
||||
let(:pat) { "fake-pat" }
|
||||
|
||||
before do
|
||||
session[:bulk_import_gitlab_access_token] = pat
|
||||
session[:bulk_import_gitlab_url] = instance_url
|
||||
end
|
||||
|
||||
it 'executes BulkImportService' do
|
||||
expect_next_instance_of(BulkImportService) do |service|
|
||||
bulk_import_params = [{ "source_type" => "group_entity",
|
||||
"source_full_path" => "full_path",
|
||||
"destination_name" =>
|
||||
"destination_name",
|
||||
"destination_namespace" => "root" }]
|
||||
|
||||
expect_next_instance_of(
|
||||
BulkImportService, user, bulk_import_params, { url: instance_url, access_token: pat }) do |service|
|
||||
expect(service).to receive(:execute)
|
||||
end
|
||||
|
||||
post :create
|
||||
post :create, params: { bulk_import: bulk_import_params }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
|
|
|
@ -12,13 +12,18 @@ describe('Blob Editing', () => {
|
|||
const useMock = jest.fn();
|
||||
const mockInstance = {
|
||||
use: useMock,
|
||||
getValue: jest.fn(),
|
||||
setValue: jest.fn(),
|
||||
getValue: jest.fn().mockReturnValue('test value'),
|
||||
focus: jest.fn(),
|
||||
};
|
||||
beforeEach(() => {
|
||||
setFixtures(
|
||||
`<div class="js-edit-blob-form"><div id="file_path"></div><div id="editor"></div><input id="file-content"></div>`,
|
||||
);
|
||||
setFixtures(`
|
||||
<form class="js-edit-blob-form">
|
||||
<div id="file_path"></div>
|
||||
<div id="editor"></div>
|
||||
<textarea id="file-content"></textarea>
|
||||
</form>
|
||||
`);
|
||||
jest.spyOn(EditorLite.prototype, 'createInstance').mockReturnValue(mockInstance);
|
||||
});
|
||||
afterEach(() => {
|
||||
|
@ -55,4 +60,15 @@ describe('Blob Editing', () => {
|
|||
expect(EditorMarkdownExtension).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('adds trailing newline to the blob content on submit', async () => {
|
||||
const form = document.querySelector('.js-edit-blob-form');
|
||||
const fileContentEl = document.getElementById('file-content');
|
||||
|
||||
await initEditor();
|
||||
|
||||
form.dispatchEvent(new Event('submit'));
|
||||
|
||||
expect(fileContentEl.value).toBe('test value\n');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -409,10 +409,13 @@ describe('DiffsStoreUtils', () => {
|
|||
diff_files: [{ ...mock, [INLINE_DIFF_LINES_KEY]: undefined }],
|
||||
};
|
||||
|
||||
preparedDiff.diff_files = utils.prepareDiffData(preparedDiff);
|
||||
splitInlineDiff.diff_files = utils.prepareDiffData(splitInlineDiff);
|
||||
splitParallelDiff.diff_files = utils.prepareDiffData(splitParallelDiff);
|
||||
completedDiff.diff_files = utils.prepareDiffData(completedDiff, [mock]);
|
||||
preparedDiff.diff_files = utils.prepareDiffData({ diff: preparedDiff });
|
||||
splitInlineDiff.diff_files = utils.prepareDiffData({ diff: splitInlineDiff });
|
||||
splitParallelDiff.diff_files = utils.prepareDiffData({ diff: splitParallelDiff });
|
||||
completedDiff.diff_files = utils.prepareDiffData({
|
||||
diff: completedDiff,
|
||||
priorFiles: [mock],
|
||||
});
|
||||
});
|
||||
|
||||
it('sets the renderIt and collapsed attribute on files', () => {
|
||||
|
@ -447,7 +450,10 @@ describe('DiffsStoreUtils', () => {
|
|||
content_sha: 'ABC',
|
||||
file_hash: 'DEF',
|
||||
};
|
||||
const updatedFilesList = utils.prepareDiffData({ diff_files: [fakeNewFile] }, priorFiles);
|
||||
const updatedFilesList = utils.prepareDiffData({
|
||||
diff: { diff_files: [fakeNewFile] },
|
||||
priorFiles,
|
||||
});
|
||||
|
||||
expect(updatedFilesList).toEqual([mock, fakeNewFile]);
|
||||
});
|
||||
|
@ -460,7 +466,10 @@ describe('DiffsStoreUtils', () => {
|
|||
{ ...mock, [INLINE_DIFF_LINES_KEY]: undefined },
|
||||
{ ...mock, [INLINE_DIFF_LINES_KEY]: undefined, content_sha: 'ABC', file_hash: 'DEF' },
|
||||
];
|
||||
const updatedFilesList = utils.prepareDiffData({ diff_files: fakeBatch }, priorFiles);
|
||||
const updatedFilesList = utils.prepareDiffData({
|
||||
diff: { diff_files: fakeBatch },
|
||||
priorFiles,
|
||||
});
|
||||
|
||||
expect(updatedFilesList).toEqual([
|
||||
mock,
|
||||
|
@ -498,7 +507,7 @@ describe('DiffsStoreUtils', () => {
|
|||
beforeEach(() => {
|
||||
mock = getDiffMetadataMock();
|
||||
|
||||
preparedDiffFiles = utils.prepareDiffData(mock);
|
||||
preparedDiffFiles = utils.prepareDiffData({ diff: mock, meta: true });
|
||||
});
|
||||
|
||||
it('sets the renderIt and collapsed attribute on files', () => {
|
||||
|
@ -514,7 +523,7 @@ describe('DiffsStoreUtils', () => {
|
|||
const fileMock = getDiffFileMock();
|
||||
const metaData = getDiffMetadataMock();
|
||||
const priorFiles = [fileMock];
|
||||
const updatedFilesList = utils.prepareDiffData(metaData, priorFiles);
|
||||
const updatedFilesList = utils.prepareDiffData({ diff: metaData, priorFiles, meta: true });
|
||||
|
||||
expect(updatedFilesList.length).toEqual(2);
|
||||
expect(updatedFilesList[0]).toEqual(fileMock);
|
||||
|
@ -539,7 +548,7 @@ describe('DiffsStoreUtils', () => {
|
|||
const fileMock = getDiffFileMock();
|
||||
const metaMock = getDiffMetadataMock();
|
||||
const priorFiles = [{ ...fileMock }];
|
||||
const updatedFilesList = utils.prepareDiffData(metaMock, priorFiles);
|
||||
const updatedFilesList = utils.prepareDiffData({ diff: metaMock, priorFiles, meta: true });
|
||||
|
||||
expect(updatedFilesList).toEqual([
|
||||
fileMock,
|
||||
|
|
|
@ -1,31 +1,42 @@
|
|||
import { prepareRawDiffFile } from '~/diffs/utils/diff_file';
|
||||
|
||||
const DIFF_FILES = [
|
||||
{
|
||||
file_hash: 'ABC', // This file is just a normal file
|
||||
},
|
||||
{
|
||||
file_hash: 'DEF', // This file replaces a symlink
|
||||
a_mode: '0',
|
||||
b_mode: '0755',
|
||||
},
|
||||
{
|
||||
file_hash: 'DEF', // This symlink is replaced by a file
|
||||
a_mode: '120000',
|
||||
b_mode: '0',
|
||||
},
|
||||
{
|
||||
file_hash: 'GHI', // This symlink replaces a file
|
||||
a_mode: '0',
|
||||
b_mode: '120000',
|
||||
},
|
||||
{
|
||||
file_hash: 'GHI', // This file is replaced by a symlink
|
||||
a_mode: '0755',
|
||||
b_mode: '0',
|
||||
},
|
||||
];
|
||||
|
||||
function getDiffFiles() {
|
||||
return [
|
||||
{
|
||||
file_hash: 'ABC', // This file is just a normal file
|
||||
file_identifier_hash: 'ABC1',
|
||||
content_sha: 'C047347',
|
||||
},
|
||||
{
|
||||
file_hash: 'DEF', // This file replaces a symlink
|
||||
file_identifier_hash: 'DEF1',
|
||||
content_sha: 'C047347',
|
||||
a_mode: '0',
|
||||
b_mode: '0755',
|
||||
},
|
||||
{
|
||||
file_hash: 'DEF', // This symlink is replaced by a file
|
||||
file_identifier_hash: 'DEF2',
|
||||
content_sha: 'C047347',
|
||||
a_mode: '120000',
|
||||
b_mode: '0',
|
||||
},
|
||||
{
|
||||
file_hash: 'GHI', // This symlink replaces a file
|
||||
file_identifier_hash: 'GHI1',
|
||||
content_sha: 'C047347',
|
||||
a_mode: '0',
|
||||
b_mode: '120000',
|
||||
},
|
||||
{
|
||||
file_hash: 'GHI', // This file is replaced by a symlink
|
||||
file_identifier_hash: 'GHI2',
|
||||
content_sha: 'C047347',
|
||||
a_mode: '0755',
|
||||
b_mode: '0',
|
||||
},
|
||||
];
|
||||
}
|
||||
function makeBrokenSymlinkObject(replaced, wasSymbolic, isSymbolic, wasReal, isReal) {
|
||||
return {
|
||||
replaced,
|
||||
|
@ -38,6 +49,12 @@ function makeBrokenSymlinkObject(replaced, wasSymbolic, isSymbolic, wasReal, isR
|
|||
|
||||
describe('diff_file utilities', () => {
|
||||
describe('prepareRawDiffFile', () => {
|
||||
let files;
|
||||
|
||||
beforeEach(() => {
|
||||
files = getDiffFiles();
|
||||
});
|
||||
|
||||
it.each`
|
||||
fileIndex | description | brokenSymlink
|
||||
${0} | ${'a file that is not symlink-adjacent'} | ${false}
|
||||
|
@ -49,12 +66,51 @@ describe('diff_file utilities', () => {
|
|||
'properly marks $description with the correct .brokenSymlink value',
|
||||
({ fileIndex, brokenSymlink }) => {
|
||||
const preppedRaw = prepareRawDiffFile({
|
||||
file: DIFF_FILES[fileIndex],
|
||||
allFiles: DIFF_FILES,
|
||||
file: files[fileIndex],
|
||||
allFiles: files,
|
||||
});
|
||||
|
||||
expect(preppedRaw.brokenSymlink).toStrictEqual(brokenSymlink);
|
||||
},
|
||||
);
|
||||
|
||||
it.each`
|
||||
fileIndex | id
|
||||
${0} | ${'e075da30-4ec7-4e1c-a505-fe0fb0efe2d8'}
|
||||
${1} | ${'5ab05419-123e-4d18-8454-0b8c3d9f3f91'}
|
||||
${2} | ${'94eb6bba-575c-4504-bd8e-5d302364d31e'}
|
||||
${3} | ${'06d669b2-29b7-4f47-9731-33fc38a8db61'}
|
||||
${4} | ${'edd3e8f9-07f9-4647-8171-544c72e5a175'}
|
||||
`('sets the file id properly { id: $id } on normal diff files', ({ fileIndex, id }) => {
|
||||
const preppedFile = prepareRawDiffFile({
|
||||
file: files[fileIndex],
|
||||
allFiles: files,
|
||||
});
|
||||
|
||||
expect(preppedFile.id).toBe(id);
|
||||
});
|
||||
|
||||
it('does not set the `id` property for metadata diff files', () => {
|
||||
const preppedFile = prepareRawDiffFile({
|
||||
file: files[0],
|
||||
allFiles: files,
|
||||
meta: true,
|
||||
});
|
||||
|
||||
expect(preppedFile).not.toHaveProp('id');
|
||||
});
|
||||
|
||||
it('does not set the id property if the file is missing a `content_sha`', () => {
|
||||
const fileMissingContentSha = { ...files[0] };
|
||||
|
||||
delete fileMissingContentSha.content_sha;
|
||||
|
||||
const preppedFile = prepareRawDiffFile({
|
||||
file: fileMissingContentSha,
|
||||
allFiles: files,
|
||||
});
|
||||
|
||||
expect(preppedFile).not.toHaveProp('id');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,7 +4,6 @@ import {
|
|||
registerLanguages,
|
||||
registerSchema,
|
||||
trimPathComponents,
|
||||
insertFinalNewline,
|
||||
trimTrailingWhitespace,
|
||||
getPathParents,
|
||||
getPathParent,
|
||||
|
@ -225,29 +224,6 @@ describe('WebIDE utils', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('addFinalNewline', () => {
|
||||
it.each`
|
||||
input | output
|
||||
${'some text'} | ${'some text\n'}
|
||||
${'some text\n'} | ${'some text\n'}
|
||||
${'some text\n\n'} | ${'some text\n\n'}
|
||||
${'some\n text'} | ${'some\n text\n'}
|
||||
`('adds a newline if it doesnt already exist for input: $input', ({ input, output }) => {
|
||||
expect(insertFinalNewline(input)).toBe(output);
|
||||
});
|
||||
|
||||
it.each`
|
||||
input | output
|
||||
${'some text'} | ${'some text\r\n'}
|
||||
${'some text\r\n'} | ${'some text\r\n'}
|
||||
${'some text\n'} | ${'some text\n\r\n'}
|
||||
${'some text\r\n\r\n'} | ${'some text\r\n\r\n'}
|
||||
${'some\r\n text'} | ${'some\r\n text\r\n'}
|
||||
`('works with CRLF newline style; input: $input', ({ input, output }) => {
|
||||
expect(insertFinalNewline(input, '\r\n')).toBe(output);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPathParents', () => {
|
||||
it.each`
|
||||
path | parents
|
||||
|
|
|
@ -340,4 +340,27 @@ describe('text_utility', () => {
|
|||
expect(textUtils.isValidSha1Hash(hash)).toBe(valid);
|
||||
});
|
||||
});
|
||||
|
||||
describe('insertFinalNewline', () => {
|
||||
it.each`
|
||||
input | output
|
||||
${'some text'} | ${'some text\n'}
|
||||
${'some text\n'} | ${'some text\n'}
|
||||
${'some text\n\n'} | ${'some text\n\n'}
|
||||
${'some\n text'} | ${'some\n text\n'}
|
||||
`('adds a newline if it doesnt already exist for input: $input', ({ input, output }) => {
|
||||
expect(textUtils.insertFinalNewline(input)).toBe(output);
|
||||
});
|
||||
|
||||
it.each`
|
||||
input | output
|
||||
${'some text'} | ${'some text\r\n'}
|
||||
${'some text\r\n'} | ${'some text\r\n'}
|
||||
${'some text\n'} | ${'some text\n\r\n'}
|
||||
${'some text\r\n\r\n'} | ${'some text\r\n\r\n'}
|
||||
${'some\r\n text'} | ${'some\r\n text\r\n'}
|
||||
`('works with CRLF newline style; input: $input', ({ input, output }) => {
|
||||
expect(textUtils.insertFinalNewline(input, '\r\n')).toBe(output);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,17 +4,75 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe Gitlab::Diff::FileCollection::Commit do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:diffable) { project.commit }
|
||||
|
||||
let(:collection_default_args) do
|
||||
{ diff_options: {} }
|
||||
end
|
||||
|
||||
it_behaves_like 'diff statistics' do
|
||||
let(:collection_default_args) do
|
||||
{ diff_options: {} }
|
||||
end
|
||||
|
||||
let(:diffable) { project.commit }
|
||||
let(:stub_path) { 'bar/branch-test.txt' }
|
||||
end
|
||||
|
||||
it_behaves_like 'unfoldable diff' do
|
||||
let(:diffable) { project.commit }
|
||||
it_behaves_like 'unfoldable diff'
|
||||
|
||||
it_behaves_like 'sortable diff files' do
|
||||
let(:diffable) { project.commit('913c66a') }
|
||||
|
||||
let(:unsorted_diff_files_paths) do
|
||||
[
|
||||
'.DS_Store',
|
||||
'CHANGELOG',
|
||||
'MAINTENANCE.md',
|
||||
'PROCESS.md',
|
||||
'VERSION',
|
||||
'encoding/feature-1.txt',
|
||||
'encoding/feature-2.txt',
|
||||
'encoding/hotfix-1.txt',
|
||||
'encoding/hotfix-2.txt',
|
||||
'encoding/russian.rb',
|
||||
'encoding/test.txt',
|
||||
'encoding/テスト.txt',
|
||||
'encoding/テスト.xls',
|
||||
'files/.DS_Store',
|
||||
'files/html/500.html',
|
||||
'files/images/logo-black.png',
|
||||
'files/images/logo-white.png',
|
||||
'files/js/application.js',
|
||||
'files/js/commit.js.coffee',
|
||||
'files/markdown/ruby-style-guide.md',
|
||||
'files/ruby/popen.rb',
|
||||
'files/ruby/regex.rb',
|
||||
'files/ruby/version_info.rb'
|
||||
]
|
||||
end
|
||||
|
||||
let(:sorted_diff_files_paths) do
|
||||
[
|
||||
'encoding/feature-1.txt',
|
||||
'encoding/feature-2.txt',
|
||||
'encoding/hotfix-1.txt',
|
||||
'encoding/hotfix-2.txt',
|
||||
'encoding/russian.rb',
|
||||
'encoding/test.txt',
|
||||
'encoding/テスト.txt',
|
||||
'encoding/テスト.xls',
|
||||
'files/html/500.html',
|
||||
'files/images/logo-black.png',
|
||||
'files/images/logo-white.png',
|
||||
'files/js/application.js',
|
||||
'files/js/commit.js.coffee',
|
||||
'files/markdown/ruby-style-guide.md',
|
||||
'files/ruby/popen.rb',
|
||||
'files/ruby/regex.rb',
|
||||
'files/ruby/version_info.rb',
|
||||
'files/.DS_Store',
|
||||
'.DS_Store',
|
||||
'CHANGELOG',
|
||||
'MAINTENANCE.md',
|
||||
'PROCESS.md',
|
||||
'VERSION'
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -27,4 +27,43 @@ RSpec.describe Gitlab::Diff::FileCollection::Compare do
|
|||
let(:diffable) { Compare.new(raw_compare, project) }
|
||||
let(:stub_path) { '.gitignore' }
|
||||
end
|
||||
|
||||
it_behaves_like 'sortable diff files' do
|
||||
let(:diffable) { Compare.new(raw_compare, project) }
|
||||
let(:collection_default_args) do
|
||||
{
|
||||
project: diffable.project,
|
||||
diff_options: {},
|
||||
diff_refs: diffable.diff_refs
|
||||
}
|
||||
end
|
||||
|
||||
let(:unsorted_diff_files_paths) do
|
||||
[
|
||||
'.DS_Store',
|
||||
'.gitignore',
|
||||
'.gitmodules',
|
||||
'Gemfile.zip',
|
||||
'files/.DS_Store',
|
||||
'files/ruby/popen.rb',
|
||||
'files/ruby/regex.rb',
|
||||
'files/ruby/version_info.rb',
|
||||
'gitlab-shell'
|
||||
]
|
||||
end
|
||||
|
||||
let(:sorted_diff_files_paths) do
|
||||
[
|
||||
'files/ruby/popen.rb',
|
||||
'files/ruby/regex.rb',
|
||||
'files/ruby/version_info.rb',
|
||||
'files/.DS_Store',
|
||||
'.DS_Store',
|
||||
'.gitignore',
|
||||
'.gitmodules',
|
||||
'Gemfile.zip',
|
||||
'gitlab-shell'
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -144,4 +144,18 @@ RSpec.describe Gitlab::Diff::FileCollection::MergeRequestDiffBatch do
|
|||
it_behaves_like 'cacheable diff collection' do
|
||||
let(:cacheable_files_count) { batch_size }
|
||||
end
|
||||
|
||||
it_behaves_like 'unsortable diff files' do
|
||||
let(:diffable) { merge_request.merge_request_diff }
|
||||
let(:collection_default_args) do
|
||||
{ diff_options: {} }
|
||||
end
|
||||
|
||||
subject do
|
||||
described_class.new(merge_request.merge_request_diff,
|
||||
batch_page,
|
||||
batch_size,
|
||||
**collection_default_args)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -54,4 +54,11 @@ RSpec.describe Gitlab::Diff::FileCollection::MergeRequestDiff do
|
|||
it 'returns a valid instance of a DiffCollection' do
|
||||
expect(diff_files).to be_a(Gitlab::Git::DiffCollection)
|
||||
end
|
||||
|
||||
it_behaves_like 'unsortable diff files' do
|
||||
let(:diffable) { merge_request.merge_request_diff }
|
||||
let(:collection_default_args) do
|
||||
{ diff_options: {} }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Diff::FileCollectionSorter do
|
||||
let(:diffs) do
|
||||
[
|
||||
double(new_path: '.dir/test', old_path: '.dir/test'),
|
||||
double(new_path: '', old_path: '.file'),
|
||||
double(new_path: '1-folder/A-file.ext', old_path: '1-folder/A-file.ext'),
|
||||
double(new_path: nil, old_path: '1-folder/M-file.ext'),
|
||||
double(new_path: '1-folder/Z-file.ext', old_path: '1-folder/Z-file.ext'),
|
||||
double(new_path: '', old_path: '1-folder/nested/A-file.ext'),
|
||||
double(new_path: '1-folder/nested/M-file.ext', old_path: '1-folder/nested/M-file.ext'),
|
||||
double(new_path: nil, old_path: '1-folder/nested/Z-file.ext'),
|
||||
double(new_path: '2-folder/A-file.ext', old_path: '2-folder/A-file.ext'),
|
||||
double(new_path: '', old_path: '2-folder/M-file.ext'),
|
||||
double(new_path: '2-folder/Z-file.ext', old_path: '2-folder/Z-file.ext'),
|
||||
double(new_path: nil, old_path: '2-folder/nested/A-file.ext'),
|
||||
double(new_path: 'A-file.ext', old_path: 'A-file.ext'),
|
||||
double(new_path: '', old_path: 'M-file.ext'),
|
||||
double(new_path: 'Z-file.ext', old_path: 'Z-file.ext')
|
||||
]
|
||||
end
|
||||
|
||||
subject { described_class.new(diffs) }
|
||||
|
||||
describe '#sort' do
|
||||
let(:sorted_files_paths) { subject.sort.map { |file| file.new_path.presence || file.old_path } }
|
||||
|
||||
it 'returns list sorted directory first' do
|
||||
expect(sorted_files_paths).to eq([
|
||||
'.dir/test',
|
||||
'1-folder/nested/A-file.ext',
|
||||
'1-folder/nested/M-file.ext',
|
||||
'1-folder/nested/Z-file.ext',
|
||||
'1-folder/A-file.ext',
|
||||
'1-folder/M-file.ext',
|
||||
'1-folder/Z-file.ext',
|
||||
'2-folder/nested/A-file.ext',
|
||||
'2-folder/A-file.ext',
|
||||
'2-folder/M-file.ext',
|
||||
'2-folder/Z-file.ext',
|
||||
'.file',
|
||||
'A-file.ext',
|
||||
'M-file.ext',
|
||||
'Z-file.ext'
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -16,28 +16,10 @@ RSpec.describe RawUsageData do
|
|||
describe '#update_sent_at!' do
|
||||
let(:raw_usage_data) { create(:raw_usage_data) }
|
||||
|
||||
context 'with save_raw_usage_data feature enabled' do
|
||||
before do
|
||||
stub_feature_flags(save_raw_usage_data: true)
|
||||
end
|
||||
it 'updates sent_at' do
|
||||
raw_usage_data.update_sent_at!
|
||||
|
||||
it 'updates sent_at' do
|
||||
raw_usage_data.update_sent_at!
|
||||
|
||||
expect(raw_usage_data.sent_at).not_to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with save_raw_usage_data feature disabled' do
|
||||
before do
|
||||
stub_feature_flags(save_raw_usage_data: false)
|
||||
end
|
||||
|
||||
it 'updates sent_at' do
|
||||
raw_usage_data.update_sent_at!
|
||||
|
||||
expect(raw_usage_data.sent_at).to be_nil
|
||||
end
|
||||
expect(raw_usage_data.sent_at).not_to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,14 +7,15 @@ RSpec.describe Import::BulkImportEntity do
|
|||
{
|
||||
'id' => 1,
|
||||
'full_name' => 'test',
|
||||
'full_path' => 'full/path/test',
|
||||
'full_path' => 'full/path/tes',
|
||||
'web_url' => 'http://web.url/path',
|
||||
'foo' => 'bar'
|
||||
}
|
||||
end
|
||||
|
||||
subject { described_class.represent(importable_data).as_json }
|
||||
|
||||
%w[id full_name full_path].each do |attribute|
|
||||
%w[id full_name full_path web_url].each do |attribute|
|
||||
it "exposes #{attribute}" do
|
||||
expect(subject[attribute.to_sym]).to eq(importable_data[attribute])
|
||||
end
|
||||
|
|
|
@ -134,10 +134,9 @@ RSpec.describe SubmitUsagePingService do
|
|||
it_behaves_like 'saves DevOps report data from the response'
|
||||
end
|
||||
|
||||
context 'with save_raw_usage_data feature enabled' do
|
||||
context 'with saving raw_usage_data' do
|
||||
before do
|
||||
stub_response(body: with_dev_ops_score_params)
|
||||
stub_feature_flags(save_raw_usage_data: true)
|
||||
end
|
||||
|
||||
it 'creates a raw_usage_data record' do
|
||||
|
@ -159,18 +158,6 @@ RSpec.describe SubmitUsagePingService do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with save_raw_usage_data feature disabled' do
|
||||
before do
|
||||
stub_response(body: with_dev_ops_score_params)
|
||||
end
|
||||
|
||||
it 'does not create a raw_usage_data record' do
|
||||
stub_feature_flags(save_raw_usage_data: false)
|
||||
|
||||
expect { subject.execute }.to change(RawUsageData, :count).by(0)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and usage ping response has unsuccessful status' do
|
||||
before do
|
||||
stub_response(body: nil, status: 504)
|
||||
|
|
|
@ -143,3 +143,55 @@ RSpec.shared_examples 'cacheable diff collection' do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'sortable diff files' do
|
||||
subject { described_class.new(diffable, **collection_default_args) }
|
||||
|
||||
describe '#raw_diff_files' do
|
||||
let(:raw_diff_files_paths) do
|
||||
subject.raw_diff_files(sorted: sorted).map { |file| file.new_path.presence || file.old_path }
|
||||
end
|
||||
|
||||
context 'when sorted is false (default)' do
|
||||
let(:sorted) { false }
|
||||
|
||||
it 'returns unsorted diff files' do
|
||||
expect(raw_diff_files_paths).to eq(unsorted_diff_files_paths)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when sorted is true' do
|
||||
let(:sorted) { true }
|
||||
|
||||
it 'returns sorted diff files' do
|
||||
expect(raw_diff_files_paths).to eq(sorted_diff_files_paths)
|
||||
end
|
||||
|
||||
context 'when sort_diffs feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(sort_diffs: false)
|
||||
end
|
||||
|
||||
it 'returns unsorted diff files' do
|
||||
expect(raw_diff_files_paths).to eq(unsorted_diff_files_paths)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'unsortable diff files' do
|
||||
subject { described_class.new(diffable, **collection_default_args) }
|
||||
|
||||
describe '#raw_diff_files' do
|
||||
it 'does not call Gitlab::Diff::FileCollectionSorter even when sorted is true' do
|
||||
# Ensure that diffable is created before expectation to ensure that we are
|
||||
# not calling it from `FileCollectionSorter` from `#raw_diff_files`.
|
||||
diffable
|
||||
|
||||
expect(Gitlab::Diff::FileCollectionSorter).not_to receive(:new)
|
||||
|
||||
subject.raw_diff_files(sorted: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue