Commit 996c6bf0 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 0a0e82d1
......@@ -142,11 +142,25 @@ export const stripHtml = (string, replace = '') => {
};
/**
* Converts snake_case string to camelCase
* Converts a snake_cased string to camelCase.
* Leading and trailing underscores are ignored.
*
* @param {*} string
* @param {String} string The snake_cased string to convert
* @returns {String} A camelCased version of the string
*
* @example
*
* // returns "aSnakeCasedString"
* convertToCamelCase('a_snake_cased_string')
*
* // returns "_leadingUnderscore"
* convertToCamelCase('_leading_underscore')
*
* // returns "trailingUnderscore_"
* convertToCamelCase('trailing_underscore_')
*/
export const convertToCamelCase = string => string.replace(/(_\w)/g, s => s[1].toUpperCase());
export const convertToCamelCase = string =>
string.replace(/([a-z0-9])_([a-z0-9])/gi, (match, p1, p2) => `${p1}${p2.toUpperCase()}`);
/**
* Converts camelCase string to snake_case
......
......@@ -20,10 +20,10 @@ export default {
},
computed: {
editLink() {
return this.release.Links?.editUrl;
return this.release._links?.editUrl;
},
selfLink() {
return this.release.Links?.self;
return this.release._links?.self;
},
},
};
......
......@@ -994,11 +994,6 @@ $ide-commit-header-height: 48px;
}
.ide-context-header {
.ide-merge-requests-dropdown.dropdown-menu {
width: 385px;
max-height: initial;
}
.avatar-container {
flex: 0 0 auto;
margin-right: 0;
......
......@@ -15,6 +15,15 @@ module Resolvers
argument :label_name, GraphQL::STRING_TYPE.to_list_type,
required: false,
description: 'Labels applied to this issue'
argument :milestone_title, GraphQL::STRING_TYPE.to_list_type,
required: false,
description: 'Milestones applied to this issue'
argument :assignee_username, GraphQL::STRING_TYPE,
required: false,
description: 'Username of a user assigned to the issues'
argument :assignee_id, GraphQL::STRING_TYPE,
required: false,
description: 'ID of a user assigned to the issues, "none" and "any" values supported'
argument :created_before, Types::TimeType,
required: false,
description: 'Issues created before this date'
......
......@@ -780,7 +780,7 @@ class Project < ApplicationRecord
end
def repository
@repository ||= Repository.new(full_path, self, disk_path: disk_path)
@repository ||= Repository.new(full_path, self, shard: repository_storage, disk_path: disk_path)
end
def cleanup
......@@ -1411,8 +1411,8 @@ class Project < ApplicationRecord
# Expires various caches before a project is renamed.
def expire_caches_before_rename(old_path)
repo = Repository.new(old_path, self)
wiki = Repository.new("#{old_path}.wiki", self)
repo = Repository.new(old_path, self, shard: repository_storage)
wiki = Repository.new("#{old_path}.wiki", self, shard: repository_storage, repo_type: Gitlab::GlRepository::WIKI)
if repo.exists?
repo.before_delete
......
......@@ -170,7 +170,7 @@ class ProjectWiki
end
def repository
@repository ||= Repository.new(full_path, @project, disk_path: disk_path, repo_type: Gitlab::GlRepository::WIKI)
@repository ||= Repository.new(full_path, @project, shard: repository_storage, disk_path: disk_path, repo_type: Gitlab::GlRepository::WIKI)
end
def default_branch
......
......@@ -22,7 +22,7 @@ class Repository
include Gitlab::RepositoryCacheAdapter
attr_accessor :full_path, :disk_path, :container, :repo_type
attr_accessor :full_path, :shard, :disk_path, :container, :repo_type
delegate :ref_name_for_sha, to: :raw_repository
delegate :bundle_to_disk, to: :raw_repository
......@@ -65,8 +65,9 @@ class Repository
xcode_config: :xcode_project?
}.freeze
def initialize(full_path, container, disk_path: nil, repo_type: Gitlab::GlRepository::PROJECT)
def initialize(full_path, container, shard:, disk_path: nil, repo_type: Gitlab::GlRepository::PROJECT)
@full_path = full_path
@shard = shard
@disk_path = disk_path || full_path
@container = container
@commit_cache = {}
......@@ -95,7 +96,7 @@ class Repository
def path_to_repo
@path_to_repo ||=
begin
storage = Gitlab.config.repositories.storages[container.repository_storage]
storage = Gitlab.config.repositories.storages[shard]
File.expand_path(
File.join(storage.legacy_disk_path, disk_path + '.git')
......@@ -1181,7 +1182,7 @@ class Repository
end
def initialize_raw_repository
Gitlab::Git::Repository.new(container.repository_storage,
Gitlab::Git::Repository.new(shard,
disk_path + '.git',
repo_type.identifier_for_container(container),
container.full_path)
......
......@@ -261,7 +261,7 @@ class Snippet < ApplicationRecord
end
def repository
@repository ||= Repository.new(full_path, self, disk_path: disk_path, repo_type: Gitlab::GlRepository::SNIPPET)
@repository ||= Repository.new(full_path, self, shard: repository_storage, disk_path: disk_path, repo_type: Gitlab::GlRepository::SNIPPET)
end
def storage
......
---
title: Search issues in GraphQL API by milestone title and assignees
merge_request: 25794
author:
type: added
......@@ -5403,6 +5403,16 @@ type Project {
A single issue of the project
"""
issue(
"""
ID of a user assigned to the issues, "none" and "any" values supported
"""
assigneeId: String
"""
Username of a user assigned to the issues
"""
assigneeUsername: String
"""
Issues closed after this date
"""
......@@ -5438,6 +5448,11 @@ type Project {
"""
labelName: [String]
"""
Milestones applied to this issue
"""
milestoneTitle: [String]
"""
Search query for finding issues by title or description
"""
......@@ -5473,6 +5488,16 @@ type Project {
"""
after: String
"""
ID of a user assigned to the issues, "none" and "any" values supported
"""
assigneeId: String
"""
Username of a user assigned to the issues
"""
assigneeUsername: String
"""
Returns the elements in the list that come before the specified cursor.
"""
......@@ -5523,6 +5548,11 @@ type Project {
"""
last: Int
"""
Milestones applied to this issue
"""
milestoneTitle: [String]
"""
Search query for finding issues by title or description
"""
......
......@@ -749,6 +749,40 @@
},
"defaultValue": null
},
{
"name": "milestoneTitle",
"description": "Milestones applied to this issue",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "assigneeUsername",
"description": "Username of a user assigned to the issues",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "assigneeId",
"description": "ID of a user assigned to the issues, \"none\" and \"any\" values supported",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "createdBefore",
"description": "Issues created before this date",
......@@ -894,6 +928,40 @@
},
"defaultValue": null
},
{
"name": "milestoneTitle",
"description": "Milestones applied to this issue",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "assigneeUsername",
"description": "Username of a user assigned to the issues",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "assigneeId",
"description": "ID of a user assigned to the issues, \"none\" and \"any\" values supported",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "createdBefore",
"description": "Issues created before this date",
......
# Group Import/Export API
> Introduced in GitLab 12.8 as an experimental feature. May change in future releases.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20353) in GitLab 12.8 as an experimental feature. May change in future releases.
Group Import/Export functionality allows to export group structure and import it at a new location.
Used in combination with [Project Import/Export](project_import_export.md) it allows you to preserve connections with group level relations
(e.g. a connection between a project issue and group epic).
Group Import/Export allows you to export group structure and import it to a new location.
When used with [Project Import/Export](project_import_export.md), you can preserve connections with
group-level relationships, such as connections between project issues and group epics.
Group Export includes:
Group exports include the following:
1. Group Milestones
1. Group Boards
1. Group Labels
1. Group Badges
1. Group Members
1. Sub-groups (each sub-group includes all data above)
- Group milestones
- Group boards
- Group labels
- Group badges
- Group members
- Sub-groups. Each sub-group includes all data above
## Schedule new export
......@@ -58,7 +58,11 @@ ls *export.tar.gz
2020-12-05_22-11-148_namespace_export.tar.gz
```
Time spent on exporting a group may vary depending on a size of the group. Export download endpoint will return exported archive once it is available. 404 is returned otherwise.
Time spent on exporting a group may vary depending on a size of the group. This endpoint
returns either:
- The exported archive (when available)
- A 404 message
## Import a file
......@@ -81,3 +85,12 @@ by `@`. For example:
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form "name=imported-group" --form "path=imported-group" --form "file=@/path/to/file" https://gitlab.example.com/api/v4/groups/import
```
## Important notes
Note the following:
- To preserve group-level relationships from imported projects, run Group Import/Export first,
to allow project imports into the desired group structure.
- Imported groups are given a `private` visibility level, unless imported into a parent group.
- If imported into a parent group, subgroups will inherit a similar level of visibility, unless otherwise restricted.
......@@ -22,7 +22,7 @@ description: 'Learn how to contribute to GitLab.'
- [Code review guidelines](code_review.md) for reviewing code and having code reviewed
- [Database review guidelines](database_review.md) for reviewing database-related changes and complex SQL queries, and having them reviewed
- [Secure coding guidlines](https://gitlab.com/gitlab-com/gl-security/security-guidelines)
- [Secure coding guidelines](https://gitlab.com/gitlab-com/gl-security/security-guidelines)
- [Pipelines for the GitLab project](pipelines.md)
Complementary reads:
......
......@@ -222,6 +222,7 @@ requirements.
1. Regressions and bugs are covered with tests that reduce the risk of the issue happening
again.
1. [Performance guidelines](../merge_request_performance_guidelines.md) have been followed.
1. [Secure coding guidelines](https://gitlab.com/gitlab-com/gl-security/security-guidelines) have been followed.
1. [Documented](../documentation/index.md) in the `/doc` directory.
1. [Changelog entry added](../changelog.md), if necessary.
1. Reviewed by relevant (UX/FE/BE/tech writing) reviewers and all concerns are addressed.
......
......@@ -12,6 +12,7 @@ See also:
- [Project import/export API](../../../api/project_import_export.md)
- [Project import/export administration rake tasks](../../../administration/raketasks/project_import_export.md) **(CORE ONLY)**
- [Group import/export API](../../../api/group_import_export.md)
To set up a project import/export:
......
......@@ -94,8 +94,27 @@ describe('text_utility', () => {
});
describe('convertToCamelCase', () => {
it('converts snake_case string to camelCase string', () => {
expect(textUtils.convertToCamelCase('snake_case')).toBe('snakeCase');
it.each`
txt | result
${'a_snake_cased_string'} | ${'aSnakeCasedString'}
${'_leading_underscore'} | ${'_leadingUnderscore'}
${'__leading_underscores'} | ${'__leadingUnderscores'}
${'trailing_underscore_'} | ${'trailingUnderscore_'}
${'trailing_underscores__'} | ${'trailingUnderscores__'}
`('converts string "$txt" to "$result"', ({ txt, result }) => {
expect(textUtils.convertToCamelCase(txt)).toBe(result);
});
it.each`
txt
${'__withoutMiddleUnderscores__'}
${''}
${'with spaces'}
${'with\nnew\r\nlines'}
${'_'}
${'___'}
`('does not modify string "$txt"', ({ txt }) => {
expect(textUtils.convertToCamelCase(txt)).toBe(txt);
});
});
......
......@@ -37,13 +37,13 @@ describe('Release block header', () => {
const link = findHeaderLink();
expect(link.text()).toBe(release.name);
expect(link.attributes('href')).toBe(release.Links.self);
expect(link.attributes('href')).toBe(release._links.self);
});
});
describe('when _links.self is missing', () => {
beforeEach(() => {
factory({ Links: { self: null } });
factory({ _links: { self: null } });
});
it('renders the title as text', () => {
......
......@@ -63,7 +63,7 @@ describe('Release block', () => {
it('renders an edit button that links to the "Edit release" page', () => {
expect(editButton().exists()).toBe(true);
expect(editButton().attributes('href')).toBe(release.Links.editUrl);
expect(editButton().attributes('href')).toBe(release._links.editUrl);
});
it('renders release name', () => {
......@@ -150,8 +150,8 @@ describe('Release block', () => {
});
});
it("does not render an edit button if release.Links.editUrl isn't a string", () => {
delete release.Links;
it("does not render an edit button if release._links.editUrl isn't a string", () => {
delete release._links;
return factory(release).then(() => {
expect(editButton().exists()).toBe(false);
......
......@@ -9,8 +9,10 @@ describe Resolvers::IssuesResolver do
context "with a project" do
let_it_be(:project) { create(:project) }
let_it_be(:issue1) { create(:issue, project: project, state: :opened, created_at: 3.hours.ago, updated_at: 3.hours.ago) }
let_it_be(:issue2) { create(:issue, project: project, state: :closed, title: 'foo', created_at: 1.hour.ago, updated_at: 1.hour.ago, closed_at: 1.hour.ago) }
let_it_be(:milestone) { create(:milestone, project: project) }
let_it_be(:assignee) { create(:user) }
let_it_be(:issue1) { create(:issue, project: project, state: :opened, created_at: 3.hours.ago, updated_at: 3.hours.ago, milestone: milestone) }
let_it_be(:issue2) { create(:issue, project: project, state: :closed, title: 'foo', created_at: 1.hour.ago, updated_at: 1.hour.ago, closed_at: 1.hour.ago, assignees: [assignee]) }
let_it_be(:label1) { create(:label, project: project) }
let_it_be(:label2) { create(:label, project: project) }
......@@ -31,6 +33,26 @@ describe Resolvers::IssuesResolver do
expect(resolve_issues(state: 'closed')).to contain_exactly(issue2)
end
it 'filters by milestone' do
expect(resolve_issues(milestone_title: milestone.title)).to contain_exactly(issue1)
end
it 'filters by assignee_username' do
expect(resolve_issues(assignee_username: assignee.username)).to contain_exactly(issue2)
end
it 'filters by assignee_id' do
expect(resolve_issues(assignee_id: assignee.id)).to contain_exactly(issue2)
end
it 'filters by any assignee' do
expect(resolve_issues(assignee_id: IssuableFinder::FILTER_ANY)).to contain_exactly(issue2)
end
it 'filters by no assignee' do
expect(resolve_issues(assignee_id: IssuableFinder::FILTER_NONE)).to contain_exactly(issue1)
end
it 'filters by labels' do
expect(resolve_issues(label_name: [label1.title])).to contain_exactly(issue1, issue2)
expect(resolve_issues(label_name: [label1.title, label2.title])).to contain_exactly(issue2)
......
......@@ -1791,21 +1791,19 @@ describe Project do
let(:project) { create(:project, :repository) }
let(:repo) { double(:repo, exists?: true) }
let(:wiki) { double(:wiki, exists?: true) }
let(:design) { double(:wiki, exists?: false) }
it 'expires the caches of the repository and wiki' do
# In EE, there are design repositories as well
allow(Repository).to receive(:new).and_call_original
allow(Repository).to receive(:new)
.with('foo', project)
.with('foo', project, shard: project.repository_storage)
.and_return(repo)
allow(Repository).to receive(:new)
.with('foo.wiki', project)
.with('foo.wiki', project, shard: project.repository_storage, repo_type: Gitlab::GlRepository::WIKI)
.and_return(wiki)
allow(Repository).to receive(:new)
.with('foo.design', project)
.and_return(design)
expect(repo).to receive(:before_delete)
expect(wiki).to receive(:before_delete)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment