Skip to content

Commit

Permalink
schema: add source workflows
Browse files Browse the repository at this point in the history
  • Loading branch information
oliver-sanders committed Oct 13, 2023
1 parent 1433411 commit 123d00d
Show file tree
Hide file tree
Showing 6 changed files with 342 additions and 14 deletions.
70 changes: 56 additions & 14 deletions cylc/uiserver/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,28 +25,37 @@
import graphene
from graphene.types.generic import GenericScalar

from cylc.flow.data_store_mgr import (
DELTA_ADDED,
)
from cylc.flow.id import Tokens
from cylc.flow.network.schema import (
CyclePoint,
GenericResponse,
ID,
SortArgs,
Task,
Mutations,
Queries,
process_resolver_info,
STRIP_NULL_DEFAULT,
SortArgs,
Subscriptions,
Task,
Workflow,
WorkflowID,
_mut_field,
get_nodes_all,
get_workflows,
process_resolver_info,
sstrip,
get_nodes_all
)
from cylc.uiserver.resolvers import (
Resolvers,
list_log_files,
stream_log,
)
from cylc.uiserver.services.source_workflows import (
list_source_workflows,
get_workflow_source,
)

if TYPE_CHECKING:
from graphql import ResolveInfo
Expand Down Expand Up @@ -484,17 +493,37 @@ class UISTask(Task):
count = graphene.Int()


class UISQueries(Queries):
class SourceWorkflow(graphene.ObjectType):
"""A Cylc workflow source directory.
This may or may not be located within the configured cylc source
directories. For workflows located outside of the configured
directories, the "name" field will allways be null.
"""
name = graphene.String(
description='The name of the source workflow'
)
path = graphene.String(
description='The location of the source workflow.'
)

class LogFiles(graphene.ObjectType):
# Example GraphiQL query:
# {
# logFiles(workflowID: "<workflow_id>", task: "<task_id>") {
# files
# }
# }
files = graphene.List(graphene.String)

class UISWorkflow(Workflow):
source = graphene.Field(
SourceWorkflow,
resolver=get_workflow_source,
)


class LogFiles(graphene.ObjectType):
files = graphene.List(graphene.String)


class UISQueries(Queries):
source_workflows = graphene.List(
SourceWorkflow,
resolver=list_source_workflows,
)
log_files = graphene.Field(
LogFiles,
description='List available job logs',
Expand All @@ -505,7 +534,6 @@ class LogFiles(graphene.ObjectType):
),
resolver=list_log_files
)

tasks = graphene.List(
UISTask,
description=Task._meta.description,
Expand All @@ -521,6 +549,20 @@ class LogFiles(graphene.ObjectType):
sort=SortArgs(default_value=None),
)

workflows = graphene.List(
UISWorkflow,
description=Workflow._meta.description,
ids=graphene.List(ID, default_value=[]),
exids=graphene.List(ID, default_value=[]),
# TODO: Change these defaults post #3500 in coordination with WUI
strip_null=graphene.Boolean(default_value=False),
delta_store=graphene.Boolean(default_value=False),
delta_type=graphene.String(default_value=DELTA_ADDED),
initial_burst=graphene.Boolean(default_value=True),
ignore_interval=graphene.Float(default_value=2.5),
resolver=get_workflows
)


class UISSubscriptions(Subscriptions):
# Example graphiql workflow log subscription:
Expand Down
15 changes: 15 additions & 0 deletions cylc/uiserver/services/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# THIS FILE IS PART OF THE CYLC WORKFLOW ENGINE.
# 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/>.
106 changes: 106 additions & 0 deletions cylc/uiserver/services/source_workflows.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# 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/>.

"""Utilities relating to the listing and management of source workflows."""

from contextlib import suppress
from pathlib import Path
from typing import Optional, List, Dict

from cylc.flow.cfgspec.glbl_cfg import glbl_cfg
from cylc.flow.id import Tokens
from cylc.flow.network.scan import scan_multi
from cylc.flow.pathutil import get_workflow_run_dir
from cylc.flow.workflow_files import get_workflow_source_dir


# the user's configured workflow source directories
SOURCE_DIRS: List[Path] = [
Path(source_dir).expanduser()
for source_dir in glbl_cfg().get(['install', 'source dirs'])
]


SourceWorkflow = Dict


def _source_workflow(source_path: Path) -> SourceWorkflow:
"""Return the fields required to resolve a SourceWorkflow.
Args:
source_path:
Path to the source workflow directory.
"""
return {
'name': _get_source_workflow_name(source_path),
'path': source_path,
}


def _blank_source_workflow() -> SourceWorkflow:
"""Return a blank source workflow.
This will be used for workflows which were not installed by "cylc install".
"""

return {'name': None, 'path': None}


def _get_source_workflow_name(source_path: Path) -> Optional[str]:
"""Return the "name" of the source workflow.
This is the "name" that can be provided to the "cylc install" command.
Args:
source_path:
Path to the source workflow directory.
Returns:
The source workflow name if the source workflow is located within
a configured source directory, else None.
"""
for source_dir in SOURCE_DIRS:
with suppress(ValueError):
return str(source_path.relative_to(source_dir))
return None


def _get_workflow_source(workflow_id):
"""Return the source workflow for the given workflow ID."""
run_dir = get_workflow_run_dir(workflow_id)
source_dir, _symlink = get_workflow_source_dir(run_dir)
if source_dir:
return _source_workflow(Path(source_dir))
return _blank_source_workflow()


async def list_source_workflows(*_) -> List[SourceWorkflow]:
"""List source workflows located in the configured source directories."""
ret = []
async for flow in scan_multi(SOURCE_DIRS):
ret.append(_source_workflow(flow['path']))
return ret


def get_workflow_source(data, _, **kwargs) -> Optional[SourceWorkflow]:
"""Resolve the source for an installed workflow.
If the source cannot be resolved, e.g. if the workflow was not installed by
"cylc install", then this will return None.
"""
workflow_id = Tokens(data.id)['workflow']
return _get_workflow_source(workflow_id)
8 changes: 8 additions & 0 deletions cylc/uiserver/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import zmq

from jupyter_server.auth.identity import User
from _pytest.monkeypatch import MonkeyPatch

from cylc.flow.cfgspec.glbl_cfg import glbl_cfg
from cylc.flow.id import Tokens
Expand Down Expand Up @@ -411,3 +412,10 @@ def _inner(cached=False):

yield _mock_glbl_cfg
rmtree(tmp_path)


@pytest.fixture(scope='module')
def mod_monkeypatch():
monkeypatch = MonkeyPatch()
yield monkeypatch
monkeypatch.undo()
14 changes: 14 additions & 0 deletions cylc/uiserver/tests/services/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# 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/>.
Loading

0 comments on commit 123d00d

Please sign in to comment.