Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
d703290a
Commit
d703290a
authored
Jul 02, 2020
by
Illya Klymov
Committed by
Nicolò Maria Mezzopera
Jul 02, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Introduce fork page components
Introduce fork groups list and item for new fork page design
parent
879d1679
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
467 additions
and
0 deletions
+467
-0
app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list.vue
.../pages/projects/forks/new/components/fork_groups_list.vue
+91
-0
app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list_item.vue
...s/projects/forks/new/components/fork_groups_list_item.vue
+147
-0
locale/gitlab.pot
locale/gitlab.pot
+18
-0
spec/frontend/pages/projects/forks/new/components/fork_groups_list_item_spec.js
...ojects/forks/new/components/fork_groups_list_item_spec.js
+78
-0
spec/frontend/pages/projects/forks/new/components/fork_groups_list_spec.js
...es/projects/forks/new/components/fork_groups_list_spec.js
+133
-0
No files found.
app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list.vue
0 → 100644
View file @
d703290a
<
script
>
import
{
GlTabs
,
GlTab
,
GlLoadingIcon
,
GlSearchBoxByType
}
from
'
@gitlab/ui
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
__
}
from
'
~/locale
'
;
import
createFlash
from
'
~/flash
'
;
import
ForkGroupsListItem
from
'
./fork_groups_list_item.vue
'
;
export
default
{
components
:
{
GlTabs
,
GlTab
,
GlLoadingIcon
,
GlSearchBoxByType
,
ForkGroupsListItem
,
},
props
:
{
hasReachedProjectLimit
:
{
type
:
Boolean
,
required
:
true
,
},
endpoint
:
{
type
:
String
,
required
:
true
,
},
},
data
()
{
return
{
namespaces
:
null
,
filter
:
''
,
};
},
computed
:
{
filteredNamespaces
()
{
return
this
.
namespaces
.
filter
(
n
=>
n
.
name
.
toLowerCase
().
includes
(
this
.
filter
.
toLowerCase
()));
},
},
mounted
()
{
this
.
loadGroups
();
},
methods
:
{
loadGroups
()
{
axios
.
get
(
this
.
endpoint
)
.
then
(
response
=>
{
this
.
namespaces
=
response
.
data
.
namespaces
;
})
.
catch
(()
=>
createFlash
(
__
(
'
There was a problem fetching groups.
'
)));
},
},
i18n
:
{
searchPlaceholder
:
__
(
'
Search by name
'
),
},
};
</
script
>
<
template
>
<gl-tabs
class=
"fork-groups"
>
<gl-tab
:title=
"__('Groups and subgroups')"
>
<gl-loading-icon
v-if=
"!namespaces"
size=
"md"
class=
"gl-mt-3"
/>
<template
v-else-if=
"namespaces.length === 0"
>
<div
class=
"gl-text-center"
>
<div
class=
"h5"
>
{{
__
(
'
No available groups to fork the project.
'
)
}}
</div>
<p
class=
"gl-mt-5"
>
{{
__
(
'
You must have permission to create a project in a group before forking.
'
)
}}
</p>
</div>
</
template
>
<div
v-else-if=
"filteredNamespaces.length === 0"
class=
"gl-text-center gl-mt-3"
>
{{ s__('GroupsTree|No groups matched your search') }}
</div>
<ul
v-else
class=
"groups-list group-list-tree"
>
<fork-groups-list-item
v-for=
"(namespace, index) in filteredNamespaces"
:key=
"index"
:group=
"namespace"
:has-reached-project-limit=
"hasReachedProjectLimit"
/>
</ul>
</gl-tab>
<
template
#tabs-end
>
<gl-search-box-by-type
v-if=
"namespaces && namespaces.length"
v-model=
"filter"
:placeholder=
"$options.i18n.searchPlaceholder"
class=
"gl-align-self-center gl-ml-auto fork-filtered-search"
/>
</
template
>
</gl-tabs>
</template>
app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list_item.vue
0 → 100644
View file @
d703290a
<
script
>
import
{
GlLink
,
GlButton
,
GlIcon
,
GlAvatar
,
GlTooltipDirective
,
GlTooltip
,
GlBadge
,
}
from
'
@gitlab/ui
'
;
import
{
VISIBILITY_TYPE_ICON
,
GROUP_VISIBILITY_TYPE
}
from
'
~/groups/constants
'
;
import
{
__
}
from
'
~/locale
'
;
import
csrf
from
'
~/lib/utils/csrf
'
;
export
default
{
components
:
{
GlIcon
,
GlAvatar
,
GlBadge
,
GlButton
,
GlTooltip
,
GlLink
,
},
directives
:
{
GlTooltip
:
GlTooltipDirective
,
},
props
:
{
group
:
{
type
:
Object
,
required
:
true
,
},
hasReachedProjectLimit
:
{
type
:
Boolean
,
required
:
true
,
},
},
data
()
{
return
{
namespaces
:
null
};
},
computed
:
{
rowClass
()
{
return
{
'
has-description
'
:
this
.
group
.
description
,
'
being-removed
'
:
this
.
isGroupPendingRemoval
,
};
},
isGroupPendingRemoval
()
{
return
this
.
group
.
marked_for_deletion
;
},
hasForkedProject
()
{
return
Boolean
(
this
.
group
.
forked_project_path
);
},
visibilityIcon
()
{
return
VISIBILITY_TYPE_ICON
[
this
.
group
.
visibility
];
},
visibilityTooltip
()
{
return
GROUP_VISIBILITY_TYPE
[
this
.
group
.
visibility
];
},
isSelectButtonDisabled
()
{
return
this
.
hasReachedProjectLimit
||
!
this
.
group
.
can_create_project
;
},
selectButtonDisabledTooltip
()
{
return
this
.
hasReachedProjectLimit
?
this
.
$options
.
i18n
.
hasReachedProjectLimitMessage
:
this
.
$options
.
i18n
.
insufficientPermissionsMessage
;
},
},
i18n
:
{
hasReachedProjectLimitMessage
:
__
(
'
You have reached your project limit
'
),
insufficientPermissionsMessage
:
__
(
'
You must have permission to create a project in a namespace before forking.
'
,
),
},
csrf
,
};
</
script
>
<
template
>
<li
:class=
"rowClass"
class=
"group-row"
>
<div
class=
"group-row-contents gl-display-flex gl-align-items-center gl-py-3 gl-pr-5"
>
<div
class=
"folder-toggle-wrap gl-mr-2 gl-display-flex gl-align-items-center"
>
<gl-icon
name=
"folder-o"
/>
</div>
<gl-link
:href=
"group.relative_path"
class=
"gl-display-none gl-flex-shrink-0 gl-display-sm-flex gl-mr-3"
>
<gl-avatar
:size=
"32"
shape=
"rect"
:entity-name=
"group.name"
:src=
"group.avatarUrl"
/>
</gl-link>
<div
class=
"gl-min-w-0 gl-display-flex gl-flex-grow-1 gl-flex-shrink-1 gl-align-items-center"
>
<div
class=
"gl-min-w-0 gl-flex-grow-1 flex-shrink-1"
>
<div
class=
"title gl-display-flex gl-align-items-center gl-flex-wrap gl-mr-3"
>
<gl-link
:href=
"group.relative_path"
class=
"gl-mt-3 gl-mr-3 gl-text-gray-900!"
>
{{
group
.
full_name
}}
</gl-link>
<gl-icon
v-gl-tooltip
.
hover
.
bottom
class=
"gl-mr-0 gl-inline-flex gl-mt-3 text-secondary"
:name=
"visibilityIcon"
:title=
"visibilityTooltip"
/>
<gl-badge
v-if=
"isGroupPendingRemoval"
variant=
"warning"
class=
"gl-display-none gl-display-sm-flex gl-mt-3 gl-mr-1"
>
{{
__
(
'
pending removal
'
)
}}
</gl-badge
>
<span
v-if=
"group.permission"
class=
"user-access-role gl-mt-3"
>
{{
group
.
permission
}}
</span>
</div>
<div
v-if=
"group.description"
class=
"description"
>
<span
v-html=
"group.markdown_description"
>
</span>
</div>
</div>
<div
class=
"gl-display-flex gl-flex-shrink-0"
>
<gl-button
v-if=
"hasForkedProject"
class=
"gl-h-7 gl-text-decoration-none!"
:href=
"group.forked_project_path"
>
{{
__
(
'
Go to fork
'
)
}}
</gl-button
>
<template
v-else
>
<div
ref=
"selectButtonWrapper"
>
<form
method=
"POST"
:action=
"group.fork_path"
>
<input
type=
"hidden"
name=
"authenticity_token"
:value=
"$options.csrf.token"
/>
<gl-button
type=
"submit"
class=
"gl-h-7 gl-text-decoration-none!"
:data-qa-name=
"group.full_name"
variant=
"success"
:disabled=
"isSelectButtonDisabled"
>
{{
__
(
'
Select
'
)
}}
</gl-button
>
</form>
</div>
<gl-tooltip
v-if=
"isSelectButtonDisabled"
:target=
"() => $refs.selectButtonWrapper"
>
{{
selectButtonDisabledTooltip
}}
</gl-tooltip>
</
template
>
</div>
</div>
</div>
</li>
</template>
locale/gitlab.pot
View file @
d703290a
...
...
@@ -10964,6 +10964,9 @@ msgstr ""
msgid "Go to find file"
msgstr ""
msgid "Go to fork"
msgstr ""
msgid "Go to issue boards"
msgstr ""
...
...
@@ -11531,6 +11534,9 @@ msgstr ""
msgid "Groups and projects"
msgstr ""
msgid "Groups and subgroups"
msgstr ""
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
...
...
@@ -15131,6 +15137,9 @@ msgstr ""
msgid "No authentication methods configured."
msgstr ""
msgid "No available groups to fork the project."
msgstr ""
msgid "No available namespaces to fork the project."
msgstr ""
...
...
@@ -19831,6 +19840,9 @@ msgstr ""
msgid "Search by author"
msgstr ""
msgid "Search by name"
msgstr ""
msgid "Search files"
msgstr ""
...
...
@@ -22979,6 +22991,9 @@ msgstr ""
msgid "There was a problem communicating with your device."
msgstr ""
msgid "There was a problem fetching groups."
msgstr ""
msgid "There was a problem fetching project branches."
msgstr ""
...
...
@@ -26256,6 +26271,9 @@ msgstr ""
msgid "You must have maintainer access to force delete a lock"
msgstr ""
msgid "You must have permission to create a project in a group before forking."
msgstr ""
msgid "You must have permission to create a project in a namespace before forking."
msgstr ""
...
...
spec/frontend/pages/projects/forks/new/components/fork_groups_list_item_spec.js
0 → 100644
View file @
d703290a
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
GlBadge
,
GlButton
,
GlLink
}
from
'
@gitlab/ui
'
;
import
ForkGroupsListItem
from
'
~/pages/projects/forks/new/components/fork_groups_list_item.vue
'
;
describe
(
'
Fork groups list item component
'
,
()
=>
{
let
wrapper
;
const
DEFAULT_PROPS
=
{
hasReachedProjectLimit
:
false
,
};
const
DEFAULT_GROUP_DATA
=
{
id
:
22
,
name
:
'
Gitlab Org
'
,
description
:
'
Ad et ipsam earum id aut nobis.
'
,
visibility
:
'
public
'
,
full_name
:
'
Gitlab Org
'
,
created_at
:
'
2020-06-22T03:32:05.664Z
'
,
updated_at
:
'
2020-06-22T03:32:05.664Z
'
,
avatar_url
:
null
,
fork_path
:
'
/twitter/typeahead-js/-/forks?namespace_key=22
'
,
forked_project_path
:
null
,
permission
:
'
Owner
'
,
relative_path
:
'
/gitlab-org
'
,
markdown_description
:
'
<p data-sourcepos="1:1-1:31" dir="auto">Ad et ipsam earum id aut nobis.</p>
'
,
can_create_project
:
true
,
marked_for_deletion
:
false
,
};
const
DUMMY_PATH
=
'
/dummy/path
'
;
const
createWrapper
=
propsData
=>
{
wrapper
=
shallowMount
(
ForkGroupsListItem
,
{
propsData
:
{
...
DEFAULT_PROPS
,
...
propsData
,
},
});
};
it
(
'
renders pending removal badge if applicable
'
,
()
=>
{
createWrapper
({
group
:
{
...
DEFAULT_GROUP_DATA
,
marked_for_deletion
:
true
}
});
expect
(
wrapper
.
find
(
GlBadge
).
text
()).
toBe
(
'
pending removal
'
);
});
it
(
'
renders go to fork button if has forked project
'
,
()
=>
{
createWrapper
({
group
:
{
...
DEFAULT_GROUP_DATA
,
forked_project_path
:
DUMMY_PATH
}
});
expect
(
wrapper
.
find
(
GlButton
).
text
()).
toBe
(
'
Go to fork
'
);
expect
(
wrapper
.
find
(
GlButton
).
attributes
().
href
).
toBe
(
DUMMY_PATH
);
});
it
(
'
renders select button if has no forked project
'
,
()
=>
{
createWrapper
({
group
:
{
...
DEFAULT_GROUP_DATA
,
forked_project_path
:
null
,
fork_path
:
DUMMY_PATH
},
});
expect
(
wrapper
.
find
(
GlButton
).
text
()).
toBe
(
'
Select
'
);
expect
(
wrapper
.
find
(
'
form
'
).
attributes
().
action
).
toBe
(
DUMMY_PATH
);
});
it
(
'
renders link to current group
'
,
()
=>
{
const
DUMMY_FULL_NAME
=
'
dummy
'
;
createWrapper
({
group
:
{
...
DEFAULT_GROUP_DATA
,
relative_path
:
DUMMY_PATH
,
full_name
:
DUMMY_FULL_NAME
},
});
expect
(
wrapper
.
findAll
(
GlLink
)
.
filter
(
w
=>
w
.
text
()
===
DUMMY_FULL_NAME
)
.
at
(
0
)
.
attributes
().
href
,
).
toBe
(
DUMMY_PATH
);
});
});
spec/frontend/pages/projects/forks/new/components/fork_groups_list_spec.js
0 → 100644
View file @
d703290a
import
AxiosMockAdapter
from
'
axios-mock-adapter
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
GlLoadingIcon
,
GlSearchBoxByType
}
from
'
@gitlab/ui
'
;
import
{
nextTick
}
from
'
vue
'
;
import
createFlash
from
'
~/flash
'
;
import
ForkGroupsList
from
'
~/pages/projects/forks/new/components/fork_groups_list.vue
'
;
import
ForkGroupsListItem
from
'
~/pages/projects/forks/new/components/fork_groups_list_item.vue
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
jest
.
mock
(
'
~/flash
'
,
()
=>
jest
.
fn
());
describe
(
'
Fork groups list component
'
,
()
=>
{
let
wrapper
;
let
axiosMock
;
const
DEFAULT_PROPS
=
{
endpoint
:
'
/dummy
'
,
hasReachedProjectLimit
:
false
,
};
const
replyWith
=
(...
args
)
=>
axiosMock
.
onGet
(
DEFAULT_PROPS
.
endpoint
).
reply
(...
args
);
const
createWrapper
=
propsData
=>
{
wrapper
=
shallowMount
(
ForkGroupsList
,
{
propsData
:
{
...
DEFAULT_PROPS
,
...
propsData
,
},
stubs
:
{
GlTabs
:
{
template
:
'
<div><slot></slot><slot name="tabs-end"></slot></div>
'
,
},
},
});
};
beforeEach
(()
=>
{
axiosMock
=
new
AxiosMockAdapter
(
axios
);
});
afterEach
(()
=>
{
axiosMock
.
reset
();
if
(
wrapper
)
{
wrapper
.
destroy
();
wrapper
=
null
;
}
});
it
(
'
fires load groups request on mount
'
,
async
()
=>
{
replyWith
(
200
,
{
namespaces
:
[]
});
createWrapper
();
await
waitForPromises
();
expect
(
axiosMock
.
history
.
get
[
0
].
url
).
toBe
(
DEFAULT_PROPS
.
endpoint
);
});
it
(
'
displays flash if loading groups fails
'
,
async
()
=>
{
replyWith
(
500
);
createWrapper
();
await
waitForPromises
();
expect
(
createFlash
).
toHaveBeenCalled
();
});
it
(
'
displays loading indicator while loading groups
'
,
()
=>
{
replyWith
(()
=>
new
Promise
(()
=>
{}));
createWrapper
();
expect
(
wrapper
.
contains
(
GlLoadingIcon
)).
toBe
(
true
);
});
it
(
'
displays empty text if no groups are available
'
,
async
()
=>
{
const
EMPTY_TEXT
=
'
No available groups to fork the project.
'
;
replyWith
(
200
,
{
namespaces
:
[]
});
createWrapper
();
await
waitForPromises
();
expect
(
wrapper
.
text
()).
toContain
(
EMPTY_TEXT
);
});
it
(
'
displays filter field when groups are available
'
,
async
()
=>
{
replyWith
(
200
,
{
namespaces
:
[{
name
:
'
dummy1
'
},
{
name
:
'
dummy2
'
}]
});
createWrapper
();
await
waitForPromises
();
expect
(
wrapper
.
contains
(
GlSearchBoxByType
)).
toBe
(
true
);
});
it
(
'
renders list items for each available group
'
,
async
()
=>
{
const
namespaces
=
[{
name
:
'
dummy1
'
},
{
name
:
'
dummy2
'
},
{
name
:
'
otherdummy
'
}];
const
hasReachedProjectLimit
=
true
;
replyWith
(
200
,
{
namespaces
});
createWrapper
({
hasReachedProjectLimit
});
await
waitForPromises
();
expect
(
wrapper
.
findAll
(
ForkGroupsListItem
)).
toHaveLength
(
namespaces
.
length
);
namespaces
.
forEach
((
namespace
,
idx
)
=>
{
expect
(
wrapper
.
findAll
(
ForkGroupsListItem
)
.
at
(
idx
)
.
props
(),
).
toStrictEqual
({
group
:
namespace
,
hasReachedProjectLimit
});
});
});
it
(
'
filters repositories on the fly
'
,
async
()
=>
{
replyWith
(
200
,
{
namespaces
:
[{
name
:
'
dummy1
'
},
{
name
:
'
dummy2
'
},
{
name
:
'
otherdummy
'
}],
});
createWrapper
();
await
waitForPromises
();
wrapper
.
find
(
GlSearchBoxByType
).
vm
.
$emit
(
'
input
'
,
'
other
'
);
await
nextTick
();
expect
(
wrapper
.
findAll
(
ForkGroupsListItem
)).
toHaveLength
(
1
);
expect
(
wrapper
.
findAll
(
ForkGroupsListItem
)
.
at
(
0
)
.
props
().
group
.
name
,
).
toBe
(
'
otherdummy
'
);
});
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment