Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-02-27 06:08:49 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-02-27 06:08:49 +0300
commit996c6bf06f602ada62e3f754c121c17051072892 (patch)
treec7568793468de0faef693795e00f34c07da8e793
parent0a0e82d1440b06650e5fc524168b1f50a8feec68 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js20
-rw-r--r--app/assets/javascripts/releases/components/release_block_header.vue4
-rw-r--r--app/assets/stylesheets/page_bundles/ide.scss5
-rw-r--r--app/graphql/resolvers/issues_resolver.rb9
-rw-r--r--app/models/project.rb6
-rw-r--r--app/models/project_wiki.rb2
-rw-r--r--app/models/repository.rb9
-rw-r--r--app/models/snippet.rb2
-rw-r--r--changelogs/unreleased/197227-milestone-tab-async.yml5
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql30
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json68
-rw-r--r--doc/api/group_import_export.md37
-rw-r--r--doc/development/README.md2
-rw-r--r--doc/development/contributing/merge_request_workflow.md1
-rw-r--r--doc/user/project/settings/import_export.md1
-rw-r--r--spec/frontend/lib/utils/text_utility_spec.js23
-rw-r--r--spec/frontend/releases/components/release_block_header_spec.js4
-rw-r--r--spec/frontend/releases/components/release_block_spec.js6
-rw-r--r--spec/graphql/resolvers/issues_resolver_spec.rb32
-rw-r--r--spec/models/project_spec.rb12
20 files changed, 227 insertions, 51 deletions
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index 9ed286826cc..f857e618d89 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -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
diff --git a/app/assets/javascripts/releases/components/release_block_header.vue b/app/assets/javascripts/releases/components/release_block_header.vue
index f0d3f3f8c1d..0bc2a5ce2eb 100644
--- a/app/assets/javascripts/releases/components/release_block_header.vue
+++ b/app/assets/javascripts/releases/components/release_block_header.vue
@@ -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;
},
},
};
diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss
index 4b838b1383e..a748c669ee8 100644
--- a/app/assets/stylesheets/page_bundles/ide.scss
+++ b/app/assets/stylesheets/page_bundles/ide.scss
@@ -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;
diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb
index 664e0955535..ae77af32b5b 100644
--- a/app/graphql/resolvers/issues_resolver.rb
+++ b/app/graphql/resolvers/issues_resolver.rb
@@ -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'
diff --git a/app/models/project.rb b/app/models/project.rb
index 6ff5016be03..0f61d32eb8d 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -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
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index 1abde5196de..7529047a73a 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -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
diff --git a/app/models/repository.rb b/app/models/repository.rb
index a53850bb068..beb65ff26fd 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -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)
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 8f162f17ac5..8bba79bd944 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -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
diff --git a/changelogs/unreleased/197227-milestone-tab-async.yml b/changelogs/unreleased/197227-milestone-tab-async.yml
new file mode 100644
index 00000000000..c2aa3dd4485
--- /dev/null
+++ b/changelogs/unreleased/197227-milestone-tab-async.yml
@@ -0,0 +1,5 @@
+---
+title: Search issues in GraphQL API by milestone title and assignees
+merge_request: 25794
+author:
+type: added
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 816512c83bb..5cf48ab901d 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -5404,6 +5404,16 @@ type 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
"""
closedAfter: Time
@@ -5439,6 +5449,11 @@ type Project {
labelName: [String]
"""
+ Milestones applied to this issue
+ """
+ milestoneTitle: [String]
+
+ """
Search query for finding issues by title or description
"""
search: String
@@ -5474,6 +5489,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.
"""
before: String
@@ -5524,6 +5549,11 @@ type Project {
last: Int
"""
+ Milestones applied to this issue
+ """
+ milestoneTitle: [String]
+
+ """
Search query for finding issues by title or description
"""
search: String
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 2053bdb9404..ba716f82630 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -750,6 +750,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",
"type": {
@@ -895,6 +929,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",
"type": {
diff --git a/doc/api/group_import_export.md b/doc/api/group_import_export.md
index c97a753d298..09bc9810615 100644
--- a/doc/api/group_import_export.md
+++ b/doc/api/group_import_export.md
@@ -1,19 +1,19 @@
# 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.
diff --git a/doc/development/README.md b/doc/development/README.md
index 039b53d7adf..d1ad9216596 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -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:
diff --git a/doc/development/contributing/merge_request_workflow.md b/doc/development/contributing/merge_request_workflow.md
index cf71d436a15..460bb6d25df 100644
--- a/doc/development/contributing/merge_request_workflow.md
+++ b/doc/development/contributing/merge_request_workflow.md
@@ -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.
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index 716078ed1d1..1a4023b6ded 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -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:
diff --git a/spec/frontend/lib/utils/text_utility_spec.js b/spec/frontend/lib/utils/text_utility_spec.js
index 803b3629524..dc8f6c64136 100644
--- a/spec/frontend/lib/utils/text_utility_spec.js
+++ b/spec/frontend/lib/utils/text_utility_spec.js
@@ -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);
});
});
diff --git a/spec/frontend/releases/components/release_block_header_spec.js b/spec/frontend/releases/components/release_block_header_spec.js
index 78adad13f69..44f6f63fa79 100644
--- a/spec/frontend/releases/components/release_block_header_spec.js
+++ b/spec/frontend/releases/components/release_block_header_spec.js
@@ -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', () => {
diff --git a/spec/frontend/releases/components/release_block_spec.js b/spec/frontend/releases/components/release_block_spec.js
index 5d365b77560..ff88e3193bc 100644
--- a/spec/frontend/releases/components/release_block_spec.js
+++ b/spec/frontend/releases/components/release_block_spec.js
@@ -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);
diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb
index 3fbb7280465..7cfef9b4cc7 100644
--- a/spec/graphql/resolvers/issues_resolver_spec.rb
+++ b/spec/graphql/resolvers/issues_resolver_spec.rb
@@ -8,11 +8,13 @@ describe Resolvers::IssuesResolver do
let(:current_user) { create(:user) }
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(:label1) { create(:label, project: project) }
- let_it_be(:label2) { create(:label, project: project) }
+ let_it_be(:project) { create(:project) }
+ 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) }
before do
project.add_developer(current_user)
@@ -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)
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 6c90a1b5614..9b1c724f0c2 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -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)