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
203660fd
Commit
203660fd
authored
May 24, 2019
by
Phil Hughes
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
EE port of vue-repo-list-backend-frontend
parent
24d58296
Changes
13
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
259 additions
and
87 deletions
+259
-87
app/assets/javascripts/repository/components/table/index.vue
app/assets/javascripts/repository/components/table/index.vue
+83
-20
app/assets/javascripts/repository/components/table/row.vue
app/assets/javascripts/repository/components/table/row.vue
+16
-4
app/assets/javascripts/repository/fragmentTypes.json
app/assets/javascripts/repository/fragmentTypes.json
+1
-0
app/assets/javascripts/repository/graphql.js
app/assets/javascripts/repository/graphql.js
+31
-34
app/assets/javascripts/repository/queries/getFiles.graphql
app/assets/javascripts/repository/queries/getFiles.graphql
+53
-5
app/assets/javascripts/repository/queries/getProjectPath.graphql
...ets/javascripts/repository/queries/getProjectPath.graphql
+3
-0
app/assets/javascripts/repository/router.js
app/assets/javascripts/repository/router.js
+6
-6
app/assets/javascripts/repository/utils/icon.js
app/assets/javascripts/repository/utils/icon.js
+1
-1
locale/gitlab.pot
locale/gitlab.pot
+9
-0
spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap
...epository/components/table/__snapshots__/row_spec.js.snap
+2
-0
spec/frontend/repository/components/table/index_spec.js
spec/frontend/repository/components/table/index_spec.js
+42
-9
spec/frontend/repository/components/table/row_spec.js
spec/frontend/repository/components/table/row_spec.js
+11
-7
spec/frontend/repository/utils/icon_spec.js
spec/frontend/repository/utils/icon_spec.js
+1
-1
No files found.
app/assets/javascripts/repository/components/table/index.vue
View file @
203660fd
<
script
>
import
{
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
createFlash
from
'
~/flash
'
;
import
{
sprintf
,
__
}
from
'
../../../locale
'
;
import
getRefMixin
from
'
../../mixins/get_ref
'
;
import
getFiles
from
'
../../queries/getFiles.graphql
'
;
import
getProjectPath
from
'
../../queries/getProjectPath.graphql
'
;
import
TableHeader
from
'
./header.vue
'
;
import
TableRow
from
'
./row.vue
'
;
const
PAGE_SIZE
=
100
;
export
default
{
components
:
{
GlLoadingIcon
,
...
...
@@ -14,14 +18,8 @@ export default {
},
mixins
:
[
getRefMixin
],
apollo
:
{
files
:
{
query
:
getFiles
,
variables
()
{
return
{
ref
:
this
.
ref
,
path
:
this
.
path
,
};
},
projectPath
:
{
query
:
getProjectPath
,
},
},
props
:
{
...
...
@@ -32,7 +30,14 @@ export default {
},
data
()
{
return
{
files
:
[],
projectPath
:
''
,
nextPageCursor
:
''
,
entries
:
{
trees
:
[],
submodules
:
[],
blobs
:
[],
},
isLoadingFiles
:
false
,
};
},
computed
:
{
...
...
@@ -42,8 +47,63 @@ export default {
{
path
:
this
.
path
,
ref
:
this
.
ref
},
);
},
isLoadingFiles
()
{
return
this
.
$apollo
.
queries
.
files
.
loading
;
},
watch
:
{
$route
:
function
routeChange
()
{
this
.
entries
.
trees
=
[];
this
.
entries
.
submodules
=
[];
this
.
entries
.
blobs
=
[];
this
.
nextPageCursor
=
''
;
this
.
fetchFiles
();
},
},
mounted
()
{
// We need to wait for `ref` and `projectPath` to be set
this
.
$nextTick
(()
=>
this
.
fetchFiles
());
},
methods
:
{
fetchFiles
()
{
this
.
isLoadingFiles
=
true
;
return
this
.
$apollo
.
query
({
query
:
getFiles
,
variables
:
{
projectPath
:
this
.
projectPath
,
ref
:
this
.
ref
,
path
:
this
.
path
,
nextPageCursor
:
this
.
nextPageCursor
,
pageSize
:
PAGE_SIZE
,
},
})
.
then
(({
data
})
=>
{
if
(
!
data
)
return
;
const
pageInfo
=
this
.
hasNextPage
(
data
.
project
.
repository
.
tree
);
this
.
isLoadingFiles
=
false
;
this
.
entries
=
Object
.
keys
(
this
.
entries
).
reduce
(
(
acc
,
key
)
=>
({
...
acc
,
[
key
]:
this
.
normalizeData
(
key
,
data
.
project
.
repository
.
tree
[
key
].
edges
),
}),
{},
);
if
(
pageInfo
&&
pageInfo
.
hasNextPage
)
{
this
.
nextPageCursor
=
pageInfo
.
endCursor
;
this
.
fetchFiles
();
}
})
.
catch
(()
=>
createFlash
(
__
(
'
An error occurding while fetching folder content.
'
)));
},
normalizeData
(
key
,
data
)
{
return
this
.
entries
[
key
].
concat
(
data
.
map
(({
node
})
=>
node
));
},
hasNextPage
(
data
)
{
return
[]
.
concat
(
data
.
trees
.
pageInfo
,
data
.
submodules
.
pageInfo
,
data
.
blobs
.
pageInfo
)
.
find
(({
hasNextPage
})
=>
hasNextPage
);
},
},
};
...
...
@@ -58,18 +118,21 @@ export default {
tableCaption
}}
</caption>
<table-header
/>
<table-header
v-once
/>
<tbody>
<table-row
v-for=
"entry in files"
:id=
"entry.id"
:key=
"entry.id"
:path=
"entry.flatPath"
:type=
"entry.type"
/>
<template
v-for=
"val in entries"
>
<table-row
v-for=
"entry in val"
:id=
"entry.id"
:key=
"`$
{entry.flatPath}-${entry.id}`"
:current-path="path"
:path="entry.flatPath"
:type="entry.type"
/>
</
template
>
</tbody>
</table>
<gl-loading-icon
v-
if
=
"isLoadingFiles"
class=
"my-3"
size=
"md"
/>
<gl-loading-icon
v-
show
=
"isLoadingFiles"
class=
"my-3"
size=
"md"
/>
</div>
</div>
</template>
app/assets/javascripts/repository/components/table/row.vue
View file @
203660fd
...
...
@@ -6,7 +6,11 @@ export default {
mixins
:
[
getRefMixin
],
props
:
{
id
:
{
type
:
Number
,
type
:
String
,
required
:
true
,
},
currentPath
:
{
type
:
String
,
required
:
true
,
},
path
:
{
...
...
@@ -26,7 +30,7 @@ export default {
return
`fa-
${
getIconName
(
this
.
type
,
this
.
path
)}
`
;
},
isFolder
()
{
return
this
.
type
===
'
folder
'
;
return
this
.
type
===
'
tree
'
;
},
isSubmodule
()
{
return
this
.
type
===
'
commit
'
;
...
...
@@ -34,6 +38,12 @@ export default {
linkComponent
()
{
return
this
.
isFolder
?
'
router-link
'
:
'
a
'
;
},
fullPath
()
{
return
this
.
path
.
replace
(
new
RegExp
(
`^
${
this
.
currentPath
}
/`
),
''
);
},
shortSha
()
{
return
this
.
id
.
slice
(
0
,
8
);
},
},
methods
:
{
openRow
()
{
...
...
@@ -49,9 +59,11 @@ export default {
<tr
v-once
:class=
"`file_$
{id}`" class="tree-item" @click="openRow">
<td
class=
"tree-item-file-name"
>
<i
:aria-label=
"type"
role=
"img"
:class=
"iconName"
class=
"fa fa-fw"
></i>
<component
:is=
"linkComponent"
:to=
"routerLinkTo"
class=
"str-truncated"
>
{{
path
}}
</component>
<component
:is=
"linkComponent"
:to=
"routerLinkTo"
class=
"str-truncated"
>
{{
fullPath
}}
</component>
<template
v-if=
"isSubmodule"
>
@
<a
href=
"#"
class=
"commit-sha"
>
{{
id
}}
</a>
@
<a
href=
"#"
class=
"commit-sha"
>
{{
shortSha
}}
</a>
</
template
>
</td>
<td
class=
"d-none d-sm-table-cell tree-commit"
></td>
...
...
app/assets/javascripts/repository/fragmentTypes.json
0 → 100644
View file @
203660fd
{
"__schema"
:{
"types"
:[{
"kind"
:
"INTERFACE"
,
"name"
:
"Entry"
,
"possibleTypes"
:[{
"name"
:
"Blob"
},{
"name"
:
"Submodule"
},{
"name"
:
"TreeEntry"
}]}]}}
app/assets/javascripts/repository/graphql.js
View file @
203660fd
import
Vue
from
'
vue
'
;
import
VueApollo
from
'
vue-apollo
'
;
import
{
IntrospectionFragmentMatcher
}
from
'
apollo-cache-inmemory
'
;
import
createDefaultClient
from
'
~/lib/graphql
'
;
import
introspectionQueryResultData
from
'
./fragmentTypes.json
'
;
Vue
.
use
(
VueApollo
);
const
defaultClient
=
createDefaultClient
({
Query
:
{
files
()
{
return
[
{
__typename
:
'
file
'
,
id
:
1
,
name
:
'
app
'
,
flatPath
:
'
app
'
,
type
:
'
folder
'
,
},
{
__typename
:
'
file
'
,
id
:
2
,
name
:
'
gitlab-svg
'
,
flatPath
:
'
gitlab-svg
'
,
type
:
'
commit
'
,
},
{
__typename
:
'
file
'
,
id
:
3
,
name
:
'
index.js
'
,
flatPath
:
'
index.js
'
,
type
:
'
blob
'
,
},
{
__typename
:
'
file
'
,
id
:
4
,
name
:
'
test.pdf
'
,
flatPath
:
'
fixtures/test.pdf
'
,
type
:
'
blob
'
,
},
];
// We create a fragment matcher so that we can create a fragment from an interface
// Without this, Apollo throws a heuristic fragment matcher warning
const
fragmentMatcher
=
new
IntrospectionFragmentMatcher
({
introspectionQueryResultData
,
});
const
defaultClient
=
createDefaultClient
(
{},
{
cacheConfig
:
{
fragmentMatcher
,
dataIdFromObject
:
obj
=>
{
// eslint-disable-next-line no-underscore-dangle
switch
(
obj
.
__typename
)
{
// We need to create a dynamic ID for each entry
// Each entry can have the same ID as the ID is a commit ID
// So we create a unique cache ID with the path and the ID
case
'
TreeEntry
'
:
case
'
Submodule
'
:
case
'
Blob
'
:
return
`
${
obj
.
flatPath
}
-
${
obj
.
id
}
`
;
default
:
// If the type doesn't match any of the above we fallback
// to using the default Apollo ID
// eslint-disable-next-line no-underscore-dangle
return
obj
.
id
||
obj
.
_id
;
}
},
},
},
}
);
);
export
default
new
VueApollo
({
defaultClient
,
...
...
app/assets/javascripts/repository/queries/getFiles.graphql
View file @
203660fd
query
getFiles
(
$path
:
String
!,
$ref
:
String
!)
{
files
(
path
:
$path
,
ref
:
$ref
)
@client
{
id
flatPath
type
fragment
TreeEntry
on
Entry
{
id
flatPath
type
}
fragment
PageInfo
on
PageInfo
{
hasNextPage
endCursor
}
query
getFiles
(
$projectPath
:
ID
!
$path
:
String
$ref
:
String
!
$pageSize
:
Int
!
$nextPageCursor
:
String
)
{
project
(
fullPath
:
$projectPath
)
{
repository
{
tree
(
path
:
$path
,
ref
:
$ref
)
{
trees
(
first
:
$pageSize
,
after
:
$nextPageCursor
)
{
edges
{
node
{
...
TreeEntry
}
}
pageInfo
{
...
PageInfo
}
}
submodules
(
first
:
$pageSize
,
after
:
$nextPageCursor
)
{
edges
{
node
{
...
TreeEntry
}
}
pageInfo
{
...
PageInfo
}
}
blobs
(
first
:
$pageSize
,
after
:
$nextPageCursor
)
{
edges
{
node
{
...
TreeEntry
}
}
pageInfo
{
...
PageInfo
}
}
}
}
}
}
app/assets/javascripts/repository/queries/getProjectPath.graphql
0 → 100644
View file @
203660fd
query
getProjectPath
{
projectPath
}
app/assets/javascripts/repository/router.js
View file @
203660fd
...
...
@@ -11,17 +11,12 @@ export default function createRouter(base, baseRef) {
mode
:
'
history
'
,
base
:
joinPaths
(
gon
.
relative_url_root
||
''
,
base
),
routes
:
[
{
path
:
'
/
'
,
name
:
'
projectRoot
'
,
component
:
IndexPage
,
},
{
path
:
`/tree/
${
baseRef
}
(/.*)?`
,
name
:
'
treePath
'
,
component
:
TreePage
,
props
:
route
=>
({
path
:
route
.
params
.
pathMatch
,
path
:
route
.
params
.
pathMatch
.
replace
(
/^
\/
/
,
''
)
,
}),
beforeEnter
(
to
,
from
,
next
)
{
document
...
...
@@ -31,6 +26,11 @@ export default function createRouter(base, baseRef) {
next
();
},
},
{
path
:
'
/
'
,
name
:
'
projectRoot
'
,
component
:
IndexPage
,
},
],
});
}
app/assets/javascripts/repository/utils/icon.js
View file @
203660fd
const
entryTypeIcons
=
{
folder
:
'
folder
'
,
tree
:
'
folder
'
,
commit
:
'
archive
'
,
};
...
...
locale/gitlab.pot
View file @
203660fd
...
...
@@ -1016,6 +1016,9 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
msgid "An error occurding while fetching folder content."
msgstr ""
msgid "An error occurred adding a draft to the discussion."
msgstr ""
...
...
@@ -7679,6 +7682,9 @@ msgstr ""
msgid "Mark as resolved"
msgstr ""
msgid "Mark comment as resolved"
msgstr ""
msgid "Mark this issue as a duplicate of another issue"
msgstr ""
...
...
@@ -10716,6 +10722,9 @@ msgstr ""
msgid "Resolved all discussions."
msgstr ""
msgid "Resolved by %{name}"
msgstr ""
msgid "Resolved by %{resolvedByName}"
msgstr ""
...
...
spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap
View file @
203660fd
...
...
@@ -16,7 +16,9 @@ exports[`Repository table row component renders table row 1`] = `
<a
class="str-truncated"
>
test
</a>
<!---->
...
...
spec/frontend/repository/components/table/index_spec.js
View file @
203660fd
...
...
@@ -3,18 +3,19 @@ import { GlLoadingIcon } from '@gitlab/ui';
import
Table
from
'
~/repository/components/table/index.vue
'
;
let
vm
;
let
$apollo
;
function
factory
(
path
,
data
=
()
=>
({}))
{
$apollo
=
{
query
:
jest
.
fn
().
mockReturnValue
(
Promise
.
resolve
({
data
:
data
()
})),
};
function
factory
(
path
,
loading
=
false
)
{
vm
=
shallowMount
(
Table
,
{
propsData
:
{
path
,
},
mocks
:
{
$apollo
:
{
queries
:
{
files
:
{
loading
},
},
},
$apollo
,
},
});
}
...
...
@@ -39,9 +40,41 @@ describe('Repository table component', () => {
);
});
it
(
'
renders loading icon
'
,
()
=>
{
factory
(
'
/
'
,
true
);
it
(
'
shows loading icon
'
,
()
=>
{
factory
(
'
/
'
);
vm
.
setData
({
isLoadingFiles
:
true
});
expect
(
vm
.
find
(
GlLoadingIcon
).
isVisible
()).
toBe
(
true
);
});
describe
(
'
normalizeData
'
,
()
=>
{
it
(
'
normalizes edge nodes
'
,
()
=>
{
const
output
=
vm
.
vm
.
normalizeData
(
'
blobs
'
,
[{
node
:
'
1
'
},
{
node
:
'
2
'
}]);
expect
(
output
).
toEqual
([
'
1
'
,
'
2
'
]);
});
});
describe
(
'
hasNextPage
'
,
()
=>
{
it
(
'
returns undefined when hasNextPage is false
'
,
()
=>
{
const
output
=
vm
.
vm
.
hasNextPage
({
trees
:
{
pageInfo
:
{
hasNextPage
:
false
}
},
submodules
:
{
pageInfo
:
{
hasNextPage
:
false
}
},
blobs
:
{
pageInfo
:
{
hasNextPage
:
false
}
},
});
expect
(
output
).
toBe
(
undefined
);
});
it
(
'
returns pageInfo object when hasNextPage is true
'
,
()
=>
{
const
output
=
vm
.
vm
.
hasNextPage
({
trees
:
{
pageInfo
:
{
hasNextPage
:
false
}
},
submodules
:
{
pageInfo
:
{
hasNextPage
:
false
}
},
blobs
:
{
pageInfo
:
{
hasNextPage
:
true
,
nextCursor
:
'
test
'
}
},
});
expect
(
vm
.
find
(
GlLoadingIcon
).
exists
()).
toBe
(
true
);
expect
(
output
).
toEqual
({
hasNextPage
:
true
,
nextCursor
:
'
test
'
});
});
});
});
spec/frontend/repository/components/table/row_spec.js
View file @
203660fd
...
...
@@ -29,9 +29,10 @@ describe('Repository table row component', () => {
it
(
'
renders table row
'
,
()
=>
{
factory
({
id
:
1
,
id
:
'
1
'
,
path
:
'
test
'
,
type
:
'
file
'
,
currentPath
:
'
/
'
,
});
expect
(
vm
.
element
).
toMatchSnapshot
();
...
...
@@ -39,14 +40,15 @@ describe('Repository table row component', () => {
it
.
each
`
type | component | componentName
${
'
folder
'
}
|
${
RouterLinkStub
}
|
${
'
RouterLink
'
}
${
'
tree
'
}
|
${
RouterLinkStub
}
|
${
'
RouterLink
'
}
${
'
file
'
}
|
${
'
a
'
}
|
${
'
hyperlink
'
}
${
'
commit
'
}
|
${
'
a
'
}
|
${
'
hyperlink
'
}
`
(
'
renders a $componentName for type $type
'
,
({
type
,
component
})
=>
{
factory
({
id
:
1
,
id
:
'
1
'
,
path
:
'
test
'
,
type
,
currentPath
:
'
/
'
,
});
expect
(
vm
.
find
(
component
).
exists
()).
toBe
(
true
);
...
...
@@ -54,14 +56,15 @@ describe('Repository table row component', () => {
it
.
each
`
type | pushes
${
'
folder
'
}
|
${
true
}
${
'
tree
'
}
|
${
true
}
${
'
file
'
}
|
${
false
}
${
'
commit
'
}
|
${
false
}
`
(
'
pushes new router if type $type is
folder
'
,
({
type
,
pushes
})
=>
{
`
(
'
pushes new router if type $type is
tree
'
,
({
type
,
pushes
})
=>
{
factory
({
id
:
1
,
id
:
'
1
'
,
path
:
'
test
'
,
type
,
currentPath
:
'
/
'
,
});
vm
.
trigger
(
'
click
'
);
...
...
@@ -75,9 +78,10 @@ describe('Repository table row component', () => {
it
(
'
renders commit ID for submodule
'
,
()
=>
{
factory
({
id
:
1
,
id
:
'
1
'
,
path
:
'
test
'
,
type
:
'
commit
'
,
currentPath
:
'
/
'
,
});
expect
(
vm
.
find
(
'
.commit-sha
'
).
text
()).
toContain
(
'
1
'
);
...
...
spec/frontend/repository/utils/icon_spec.js
View file @
203660fd
...
...
@@ -6,7 +6,7 @@ describe('getIconName', () => {
// file types
it
.
each
`
type | path | icon
${
'
folder
'
}
|
${
''
}
|
${
'
folder
'
}
${
'
tree
'
}
|
${
''
}
|
${
'
folder
'
}
${
'
commit
'
}
|
${
''
}
|
${
'
archive
'
}
${
'
file
'
}
|
${
'
test.pdf
'
}
|
${
'
file-pdf-o
'
}
${
'
file
'
}
|
${
'
test.jpg
'
}
|
${
'
file-image-o
'
}
...
...
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