Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
李少辉-开发者
gitlab-foss
提交
d8bb8d45
G
gitlab-foss
项目概览
李少辉-开发者
/
gitlab-foss
通知
15
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
G
gitlab-foss
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
未验证
提交
d8bb8d45
编写于
5月 24, 2019
作者:
P
Phil Hughes
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Pull files for repository tree from GraphQL API
上级
c509b35b
变更
13
隐藏空白更改
内联
并排
Showing
13 changed file
with
253 addition
and
87 deletion
+253
-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
+3
-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
未找到文件。
app/assets/javascripts/repository/components/table/index.vue
浏览文件 @
d8bb8d45
<
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
浏览文件 @
d8bb8d45
...
...
@@ -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
浏览文件 @
d8bb8d45
{
"__schema"
:{
"types"
:[{
"kind"
:
"INTERFACE"
,
"name"
:
"Entry"
,
"possibleTypes"
:[{
"name"
:
"Blob"
},{
"name"
:
"Submodule"
},{
"name"
:
"TreeEntry"
}]}]}}
app/assets/javascripts/repository/graphql.js
浏览文件 @
d8bb8d45
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
浏览文件 @
d8bb8d45
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
浏览文件 @
d8bb8d45
query
getProjectPath
{
projectPath
}
app/assets/javascripts/repository/router.js
浏览文件 @
d8bb8d45
...
...
@@ -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
浏览文件 @
d8bb8d45
const
entryTypeIcons
=
{
folder
:
'
folder
'
,
tree
:
'
folder
'
,
commit
:
'
archive
'
,
};
...
...
locale/gitlab.pot
浏览文件 @
d8bb8d45
...
...
@@ -835,6 +835,9 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
msgid "An error occurding while fetching folder content."
msgstr ""
msgid "An error occurred creating the new branch."
msgstr ""
...
...
spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap
浏览文件 @
d8bb8d45
...
...
@@ -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
浏览文件 @
d8bb8d45
...
...
@@ -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
浏览文件 @
d8bb8d45
...
...
@@ -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
浏览文件 @
d8bb8d45
...
...
@@ -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
'
}
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录