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
09d02ed1
Commit
09d02ed1
authored
Mar 13, 2020
by
Nicolò Maria Mezzopera
Committed by
Paul Slaughter
Mar 13, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor notebook_viewer to vue SFC
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/27090
parent
34e90bca
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
245 additions
and
209 deletions
+245
-209
app/assets/javascripts/blob/notebook/index.js
app/assets/javascripts/blob/notebook/index.js
+8
-78
app/assets/javascripts/blob/notebook/notebook_viewer.vue
app/assets/javascripts/blob/notebook/notebook_viewer.vue
+81
-0
locale/gitlab.pot
locale/gitlab.pot
+6
-0
spec/features/projects/blobs/blob_show_spec.rb
spec/features/projects/blobs/blob_show_spec.rb
+42
-0
spec/frontend/blob/notebook/notebook_viever_spec.js
spec/frontend/blob/notebook/notebook_viever_spec.js
+108
-0
spec/frontend/fixtures/static/notebook_viewer.html
spec/frontend/fixtures/static/notebook_viewer.html
+0
-1
spec/javascripts/blob/notebook/index_spec.js
spec/javascripts/blob/notebook/index_spec.js
+0
-130
No files found.
app/assets/javascripts/blob/notebook/index.js
View file @
09d02ed1
/* eslint-disable no-new */
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
axios
from
'
../../lib/utils/axios_utils
'
;
import
NotebookViewer
from
'
./notebook_viewer.vue
'
;
import
notebookLab
from
'
../../notebook/index.vue
'
;
export
default
()
=>
{
export
default
()
=>
{
const
el
=
document
.
getElementById
(
'
js-notebook-viewer
'
);
const
el
=
document
.
getElementById
(
'
js-notebook-viewer
'
);
new
Vue
({
return
new
Vue
({
el
,
el
,
components
:
{
render
(
createElement
)
{
notebookLab
,
return
createElement
(
NotebookViewer
,
{
props
:
{
endpoint
:
el
.
dataset
.
endpoint
,
},
});
},
},
data
()
{
return
{
error
:
false
,
loadError
:
false
,
loading
:
true
,
json
:
{},
};
},
mounted
()
{
if
(
gon
.
katex_css_url
)
{
const
katexStyles
=
document
.
createElement
(
'
link
'
);
katexStyles
.
setAttribute
(
'
rel
'
,
'
stylesheet
'
);
katexStyles
.
setAttribute
(
'
href
'
,
gon
.
katex_css_url
);
document
.
head
.
appendChild
(
katexStyles
);
}
if
(
gon
.
katex_js_url
)
{
const
katexScript
=
document
.
createElement
(
'
script
'
);
katexScript
.
addEventListener
(
'
load
'
,
()
=>
{
this
.
loadFile
();
});
katexScript
.
setAttribute
(
'
src
'
,
gon
.
katex_js_url
);
document
.
head
.
appendChild
(
katexScript
);
}
else
{
this
.
loadFile
();
}
},
methods
:
{
loadFile
()
{
axios
.
get
(
el
.
dataset
.
endpoint
)
.
then
(
res
=>
res
.
data
)
.
then
(
data
=>
{
this
.
json
=
data
;
this
.
loading
=
false
;
})
.
catch
(
e
=>
{
if
(
e
.
status
!==
200
)
{
this
.
loadError
=
true
;
}
this
.
error
=
true
;
});
},
},
template
:
`
<div class="container-fluid md prepend-top-default append-bottom-default">
<div
class="text-center loading"
v-if="loading && !error">
<i
class="fa fa-spinner fa-spin"
aria-hidden="true"
aria-label="iPython notebook loading">
</i>
</div>
<notebook-lab
v-if="!loading && !error"
:notebook="json"
code-css-class="code white" />
<p
class="text-center"
v-if="error">
<span v-if="loadError">
An error occurred while loading the file. Please try again later.
</span>
<span v-else>
An error occurred while parsing the file.
</span>
</p>
</div>
`
,
});
});
};
};
app/assets/javascripts/blob/notebook/notebook_viewer.vue
0 → 100644
View file @
09d02ed1
<
script
>
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
notebookLab
from
'
~/notebook/index.vue
'
;
import
{
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
export
default
{
components
:
{
notebookLab
,
GlLoadingIcon
,
},
props
:
{
endpoint
:
{
type
:
String
,
required
:
true
,
},
},
data
()
{
return
{
error
:
false
,
loadError
:
false
,
loading
:
true
,
json
:
{},
};
},
mounted
()
{
if
(
gon
.
katex_css_url
)
{
const
katexStyles
=
document
.
createElement
(
'
link
'
);
katexStyles
.
setAttribute
(
'
rel
'
,
'
stylesheet
'
);
katexStyles
.
setAttribute
(
'
href
'
,
gon
.
katex_css_url
);
document
.
head
.
appendChild
(
katexStyles
);
}
if
(
gon
.
katex_js_url
)
{
const
katexScript
=
document
.
createElement
(
'
script
'
);
katexScript
.
addEventListener
(
'
load
'
,
()
=>
{
this
.
loadFile
();
});
katexScript
.
setAttribute
(
'
src
'
,
gon
.
katex_js_url
);
document
.
head
.
appendChild
(
katexScript
);
}
else
{
this
.
loadFile
();
}
},
methods
:
{
loadFile
()
{
axios
.
get
(
this
.
endpoint
)
.
then
(
res
=>
res
.
data
)
.
then
(
data
=>
{
this
.
json
=
data
;
this
.
loading
=
false
;
})
.
catch
(
e
=>
{
if
(
e
.
status
!==
200
)
{
this
.
loadError
=
true
;
}
this
.
error
=
true
;
});
},
},
};
</
script
>
<
template
>
<div
class=
"js-notebook-viewer-mounted container-fluid md prepend-top-default append-bottom-default"
>
<div
v-if=
"loading && !error"
class=
"text-center loading"
>
<gl-loading-icon
class=
"mt-5"
size=
"lg"
/>
</div>
<notebook-lab
v-if=
"!loading && !error"
:notebook=
"json"
code-css-class=
"code white"
/>
<p
v-if=
"error"
class=
"text-center"
>
<span
v-if=
"loadError"
ref=
"loadErrorMessage"
>
{{
__
(
'
An error occurred while loading the file. Please try again later.
'
)
}}
</span>
<span
v-else
ref=
"parsingErrorMessage"
>
{{
__
(
'
An error occurred while parsing the file.
'
)
}}
</span>
</p>
</div>
</
template
>
locale/gitlab.pot
View file @
09d02ed1
...
@@ -1916,6 +1916,9 @@ msgstr ""
...
@@ -1916,6 +1916,9 @@ msgstr ""
msgid "An error occurred while loading the file."
msgid "An error occurred while loading the file."
msgstr ""
msgstr ""
msgid "An error occurred while loading the file. Please try again later."
msgstr ""
msgid "An error occurred while loading the merge request changes."
msgid "An error occurred while loading the merge request changes."
msgstr ""
msgstr ""
...
@@ -1940,6 +1943,9 @@ msgstr ""
...
@@ -1940,6 +1943,9 @@ msgstr ""
msgid "An error occurred while parsing recent searches"
msgid "An error occurred while parsing recent searches"
msgstr ""
msgstr ""
msgid "An error occurred while parsing the file."
msgstr ""
msgid "An error occurred while removing epics."
msgid "An error occurred while removing epics."
msgstr ""
msgstr ""
...
...
spec/features/projects/blobs/blob_show_spec.rb
View file @
09d02ed1
...
@@ -308,6 +308,48 @@ describe 'File blob', :js do
...
@@ -308,6 +308,48 @@ describe 'File blob', :js do
end
end
end
end
context
'Jupiter Notebook file'
do
before
do
project
.
add_maintainer
(
project
.
creator
)
Files
::
CreateService
.
new
(
project
,
project
.
creator
,
start_branch:
'master'
,
branch_name:
'master'
,
commit_message:
"Add Jupiter Notebook"
,
file_path:
'files/basic.ipynb'
,
file_content:
project
.
repository
.
blob_at
(
'add-ipython-files'
,
'files/ipython/basic.ipynb'
).
data
).
execute
visit_blob
(
'files/basic.ipynb'
)
wait_for_requests
end
it
'displays the blob'
do
aggregate_failures
do
# shows rendered notebook
expect
(
page
).
to
have_selector
(
'.js-notebook-viewer-mounted'
)
# does show a viewer switcher
expect
(
page
).
to
have_selector
(
'.js-blob-viewer-switcher'
)
# show a disabled copy button
expect
(
page
).
to
have_selector
(
'.js-copy-blob-source-btn.disabled'
)
# shows a raw button
expect
(
page
).
to
have_link
(
'Open raw'
)
# shows a download button
expect
(
page
).
to
have_link
(
'Download'
)
# shows the rendered notebook
expect
(
page
).
to
have_content
(
'test'
)
end
end
end
context
'ISO file (stored in LFS)'
do
context
'ISO file (stored in LFS)'
do
context
'when LFS is enabled on the project'
do
context
'when LFS is enabled on the project'
do
before
do
before
do
...
...
spec/frontend/blob/notebook/notebook_viever_spec.js
0 → 100644
View file @
09d02ed1
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
component
from
'
~/blob/notebook/notebook_viewer.vue
'
;
import
NotebookLab
from
'
~/notebook/index.vue
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
describe
(
'
iPython notebook renderer
'
,
()
=>
{
let
wrapper
;
let
mock
;
const
endpoint
=
'
test
'
;
const
mockNotebook
=
{
cells
:
[
{
cell_type
:
'
markdown
'
,
source
:
[
'
# test
'
],
},
{
cell_type
:
'
code
'
,
execution_count
:
1
,
source
:
[
'
def test(str)
'
,
'
return str
'
],
outputs
:
[],
},
],
};
const
mountComponent
=
()
=>
{
wrapper
=
shallowMount
(
component
,
{
propsData
:
{
endpoint
}
});
};
const
findLoading
=
()
=>
wrapper
.
find
(
GlLoadingIcon
);
const
findNotebookLab
=
()
=>
wrapper
.
find
(
NotebookLab
);
const
findLoadErrorMessage
=
()
=>
wrapper
.
find
({
ref
:
'
loadErrorMessage
'
});
const
findParseErrorMessage
=
()
=>
wrapper
.
find
({
ref
:
'
parsingErrorMessage
'
});
beforeEach
(()
=>
{
mock
=
new
MockAdapter
(
axios
);
});
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
mock
.
restore
();
});
it
(
'
shows loading icon
'
,
()
=>
{
mock
.
onGet
(
endpoint
).
reply
(()
=>
new
Promise
(()
=>
{}));
mountComponent
({
loadFile
:
jest
.
fn
()
});
expect
(
findLoading
().
exists
()).
toBe
(
true
);
});
describe
(
'
successful response
'
,
()
=>
{
beforeEach
(()
=>
{
mock
.
onGet
(
endpoint
).
reply
(
200
,
mockNotebook
);
mountComponent
();
return
waitForPromises
();
});
it
(
'
does not show loading icon
'
,
()
=>
{
expect
(
findLoading
().
exists
()).
toBe
(
false
);
});
it
(
'
renders the notebook
'
,
()
=>
{
expect
(
findNotebookLab
().
exists
()).
toBe
(
true
);
});
});
describe
(
'
error in JSON response
'
,
()
=>
{
beforeEach
(()
=>
{
mock
.
onGet
(
endpoint
).
reply
(()
=>
// eslint-disable-next-line prefer-promise-reject-errors
Promise
.
reject
({
status
:
200
}),
);
mountComponent
();
return
waitForPromises
();
});
it
(
'
does not show loading icon
'
,
()
=>
{
expect
(
findLoading
().
exists
()).
toBe
(
false
);
});
it
(
'
shows error message
'
,
()
=>
{
expect
(
findParseErrorMessage
().
text
()).
toEqual
(
'
An error occurred while parsing the file.
'
);
});
});
describe
(
'
error getting file
'
,
()
=>
{
beforeEach
(()
=>
{
mock
.
onGet
(
endpoint
).
reply
(
500
,
''
);
mountComponent
();
return
waitForPromises
();
});
it
(
'
does not show loading icon
'
,
()
=>
{
expect
(
findLoading
().
exists
()).
toBe
(
false
);
});
it
(
'
shows error message
'
,
()
=>
{
expect
(
findLoadErrorMessage
().
text
()).
toEqual
(
'
An error occurred while loading the file. Please try again later.
'
,
);
});
});
});
spec/frontend/fixtures/static/notebook_viewer.html
deleted
100644 → 0
View file @
34e90bca
<div
class=
"file-content"
data-endpoint=
"/test"
id=
"js-notebook-viewer"
></div>
spec/javascripts/blob/notebook/index_spec.js
deleted
100644 → 0
View file @
34e90bca
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
renderNotebook
from
'
~/blob/notebook
'
;
describe
(
'
iPython notebook renderer
'
,
()
=>
{
preloadFixtures
(
'
static/notebook_viewer.html
'
);
beforeEach
(()
=>
{
loadFixtures
(
'
static/notebook_viewer.html
'
);
});
it
(
'
shows loading icon
'
,
()
=>
{
renderNotebook
();
expect
(
document
.
querySelector
(
'
.loading
'
)).
not
.
toBeNull
();
});
describe
(
'
successful response
'
,
()
=>
{
let
mock
;
beforeEach
(
done
=>
{
mock
=
new
MockAdapter
(
axios
);
mock
.
onGet
(
'
/test
'
).
reply
(
200
,
{
cells
:
[
{
cell_type
:
'
markdown
'
,
source
:
[
'
# test
'
],
},
{
cell_type
:
'
code
'
,
execution_count
:
1
,
source
:
[
'
def test(str)
'
,
'
return str
'
],
outputs
:
[],
},
],
});
renderNotebook
();
setTimeout
(()
=>
{
done
();
});
});
afterEach
(()
=>
{
mock
.
restore
();
});
it
(
'
does not show loading icon
'
,
()
=>
{
expect
(
document
.
querySelector
(
'
.loading
'
)).
toBeNull
();
});
it
(
'
renders the notebook
'
,
()
=>
{
expect
(
document
.
querySelector
(
'
.md
'
)).
not
.
toBeNull
();
});
it
(
'
renders the markdown cell
'
,
()
=>
{
expect
(
document
.
querySelector
(
'
h1
'
)).
not
.
toBeNull
();
expect
(
document
.
querySelector
(
'
h1
'
).
textContent
.
trim
()).
toBe
(
'
test
'
);
});
it
(
'
highlights code
'
,
()
=>
{
expect
(
document
.
querySelector
(
'
.token
'
)).
not
.
toBeNull
();
expect
(
document
.
querySelector
(
'
.language-python
'
)).
not
.
toBeNull
();
});
});
describe
(
'
error in JSON response
'
,
()
=>
{
let
mock
;
beforeEach
(
done
=>
{
mock
=
new
MockAdapter
(
axios
);
mock
.
onGet
(
'
/test
'
).
reply
(()
=>
// eslint-disable-next-line prefer-promise-reject-errors
Promise
.
reject
({
status
:
200
,
data
:
'
{ "cells": [{"cell_type": "markdown"} }
'
}),
);
renderNotebook
();
setTimeout
(()
=>
{
done
();
});
});
afterEach
(()
=>
{
mock
.
restore
();
});
it
(
'
does not show loading icon
'
,
()
=>
{
expect
(
document
.
querySelector
(
'
.loading
'
)).
toBeNull
();
});
it
(
'
shows error message
'
,
()
=>
{
expect
(
document
.
querySelector
(
'
.md
'
).
textContent
.
trim
()).
toBe
(
'
An error occurred while parsing the file.
'
,
);
});
});
describe
(
'
error getting file
'
,
()
=>
{
let
mock
;
beforeEach
(
done
=>
{
mock
=
new
MockAdapter
(
axios
);
mock
.
onGet
(
'
/test
'
).
reply
(
500
,
''
);
renderNotebook
();
setTimeout
(()
=>
{
done
();
});
});
afterEach
(()
=>
{
mock
.
restore
();
});
it
(
'
does not show loading icon
'
,
()
=>
{
expect
(
document
.
querySelector
(
'
.loading
'
)).
toBeNull
();
});
it
(
'
shows error message
'
,
()
=>
{
expect
(
document
.
querySelector
(
'
.md
'
).
textContent
.
trim
()).
toBe
(
'
An error occurred while loading the file. Please try again later.
'
,
);
});
});
});
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