Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lab: open log files in Jupyter Lab #2092

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
lab: open log files in Jupyter Lab
* Offers an "open in Jupyter Lab" button in the Log view (when Jupyter
  Lab is installed).
* Works with https://github.com/oliver-sanders/cylc-jupyterlab-extension
oliver-sanders committed Mar 10, 2025
commit bbd27c32cbee00ede787f0de9e7ab24d271bd140
5 changes: 4 additions & 1 deletion src/components/Markdown.vue
Original file line number Diff line number Diff line change
@@ -26,7 +26,10 @@
<script>
import MarkdownIt from 'markdown-it'

const md = new MarkdownIt()
const md = new MarkdownIt({
// allow HTML syntax
html: true,
})

export default {
name: 'Markdown',
16 changes: 15 additions & 1 deletion src/components/core/Alert.vue
Original file line number Diff line number Diff line change
@@ -34,21 +34,35 @@
<v-icon>{{ $options.icons.mdiClose }}</v-icon>
</v-btn>
</template>
{{ alert.text }}
<Markdown v-if="alert.text" :markdown="String(alert.text)" />
</v-snackbar>
</template>

<script>
import { mdiClose } from '@mdi/js'
import { mapActions, mapState } from 'vuex'
import Markdown from '@/components/Markdown.vue'

export default {
name: 'Alert',

components: {
Markdown,
},

computed: {
...mapState(['alert'])
},

watch: {
async alert (val, old) {
if (val && val !== old) {
await new Promise(resolve => setTimeout(resolve, val.timeout))
this.closeAlert()

Check warning on line 61 in src/components/core/Alert.vue

Codecov / codecov/patch

src/components/core/Alert.vue#L61

Added line #L61 was not covered by tests
}
}
},

methods: {
...mapActions(['setAlert']),
/**
93 changes: 93 additions & 0 deletions src/components/cylc/JupyterLauncher.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<!--
Copyright (C) NIWA & British Crown (Met Office) & Contributors.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->

<template class="c-jupyter-launcher">
<v-chip
variant="outlined"
class="flex-shrink-0"
@click="open"
v-if="user.extensions?.lab"
>
<v-icon style="margin-right: 0.5em">{{ $options.icons.jupyterLogo }}</v-icon>
Open in Jupyter Lab
</v-chip>
</template>

<script>
import axios from 'axios'
import { store } from '@/store/index'
import { Alert as AlertModel } from '@/model/Alert.model'
import { mapState } from 'vuex'
import { createUrl, getCylcHeaders } from '@/utils/urls'
import { jupyterLogo } from '@/utils/icons'

const LAB_ENDPOINT = 'lab-bridge/open/'

export default {
name: 'JupyterLauncher',

components: {},

props: {
path: {
type: String,
required: true,
}
},

icons: {
jupyterLogo,
},

computed: {
...mapState('user', ['user']),

openInExistingSession () {
return `${LAB_ENDPOINT}/${this.path}`

Check warning on line 60 in src/components/cylc/JupyterLauncher.vue

Codecov / codecov/patch

src/components/cylc/JupyterLauncher.vue#L60

Added line #L60 was not covered by tests
},

openInNewSession () {
// TODO: Strip $HOME from the path and add it onto the URL like so:
// return `${this.user.extensions?.lab}/tree/${this.path}`
return this.user.extensions?.lab

Check warning on line 66 in src/components/cylc/JupyterLauncher.vue

Codecov / codecov/patch

src/components/cylc/JupyterLauncher.vue#L66

Added line #L66 was not covered by tests
}
},

methods: {
async open () {
await axios.get(

Check warning on line 72 in src/components/cylc/JupyterLauncher.vue

Codecov / codecov/patch

src/components/cylc/JupyterLauncher.vue#L72

Added line #L72 was not covered by tests
createUrl(this.openInExistingSession),
{ headers: getCylcHeaders() }
)
store.dispatch(

Check warning on line 76 in src/components/cylc/JupyterLauncher.vue

Codecov / codecov/patch

src/components/cylc/JupyterLauncher.vue#L76

Added line #L76 was not covered by tests
'setAlert',
new AlertModel(
// `Opened file in any existing Lab session\n\n[Open a new Jupyter Lab session!](${this.openInNewSession}){:target="_blank"}`,
'Sent request to open file in any active Jupyter Lab sessions.',
'success',
(
'Sent request to open file in any active Jupyter Lab sessions.' +
'<br /><br />' +
`<a href="${this.openInNewSession}" target="_blank">Open a new Jupyter Lab session</a>`
),
4000,
)
)
},
},
}
</script>
3 changes: 2 additions & 1 deletion src/model/Alert.model.js
Original file line number Diff line number Diff line change
@@ -22,9 +22,10 @@ export class Alert {
* @param {string} color - color of the displayed alert.
* @param {?string} msg - a custom message to display in the alert instead of err.
*/
constructor (err, color, msg = null) {
constructor (err, color, msg = null, timeout = 10000) {
this.err = err
this.text = msg || err
this.color = color
this.timeout = timeout
}
}
8 changes: 8 additions & 0 deletions src/views/Log.vue
Original file line number Diff line number Diff line change
@@ -125,6 +125,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
>
{{ results.connected ? 'Connected' : 'Reconnect' }}
</v-chip>
<JupyterLauncher :path="urlSafePath" style="margin-left: 0.5em" />
<div
data-cy="log-path"
class="ml-2 mr-1 d-flex text-medium-emphasis text-pre overflow-x-hidden"
@@ -205,6 +206,7 @@ import ViewToolbar from '@/components/cylc/ViewToolbar.vue'
import DeltasCallback from '@/services/callbacks'
import { debounce } from 'lodash-es'
import CopyBtn from '@/components/core/CopyBtn.vue'
import JupyterLauncher from '@/components/cylc/JupyterLauncher.vue'

/**
* Query used to retrieve data for the Log view.
@@ -324,6 +326,7 @@ export default {

components: {
CopyBtn,
JupyterLauncher,
LogComponent,
ViewToolbar,
},
@@ -370,6 +373,10 @@ export default {
() => results.value.path?.substring(0, results.value.path.length - file.value.length - 1)
)

const urlSafePath = computed(
() => encodeURIComponent(`${parentPath.value}/${file.value}`)
)

whenever(
() => store.state.offline,
() => { results.value.connected = false }
@@ -406,6 +413,7 @@ export default {
debouncedUpdateRelativeID,
toolbarBtnSize,
toolbarBtnProps: btnProps(toolbarBtnSize),
urlSafePath,
}
},