diff --git a/CHANGES.rst b/CHANGES.rst index 18710d029..2ed5bef5e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -12,7 +12,7 @@ Changes Changes: -------- -- No change. +- Add `Job` status `HTML` response (resolves `#779 `_). Fixes: ------ diff --git a/weaver/wps_restapi/jobs/jobs.py b/weaver/wps_restapi/jobs/jobs.py index e507e2826..9210adfcb 100644 --- a/weaver/wps_restapi/jobs/jobs.py +++ b/weaver/wps_restapi/jobs/jobs.py @@ -317,6 +317,16 @@ def trigger_job_execution(request): return submit_job_dispatch_task(job, container=request, force_submit=True) +@sd.provider_jobs_service.get( + tags=[sd.TAG_JOBS, sd.TAG_STATUS, sd.TAG_PROVIDERS], + schema=sd.GetProviderJobEndpoint(), + accept=ContentType.TEXT_HTML, + renderer="weaver.wps_restapi:templates/responses/job_status.mako", + response_schemas=sd.derive_responses( + sd.get_provider_single_job_status_responses, + sd.GenericHTMLResponse(name="HTMLProviderJobStatus", description="Job status.") + ), +) @sd.provider_job_service.get( tags=[sd.TAG_JOBS, sd.TAG_STATUS, sd.TAG_PROVIDERS], schema=sd.GetProviderJobEndpoint(), @@ -328,7 +338,17 @@ def trigger_job_execution(request): response_schemas=sd.get_provider_single_job_status_responses, ) @sd.process_job_service.get( - tags=[sd.TAG_PROCESSES, sd.TAG_JOBS, sd.TAG_STATUS], + tags=[sd.TAG_JOBS, sd.TAG_STATUS, sd.TAG_PROCESSES], + schema=sd.GetProcessJobEndpoint(), + accept=ContentType.TEXT_HTML, + renderer="weaver.wps_restapi:templates/responses/job_status.mako", + response_schemas=sd.derive_responses( + sd.get_single_job_status_responses, + sd.GenericHTMLResponse(name="HTMLProcessJobStatus", description="Job status.") + ), +) +@sd.process_job_service.get( + tags=[sd.TAG_JOBS, sd.TAG_STATUS, sd.TAG_PROCESSES], schema=sd.GetProcessJobEndpoint(), accept=[ContentType.APP_JSON] + [ f"{ContentType.APP_JSON}; profile={profile}" @@ -337,6 +357,16 @@ def trigger_job_execution(request): renderer=OutputFormat.JSON, response_schemas=sd.get_single_job_status_responses, ) +@sd.job_service.get( + tags=[sd.TAG_JOBS, sd.TAG_STATUS], + schema=sd.GetJobEndpoint(), + accept=ContentType.TEXT_HTML, + renderer="weaver.wps_restapi:templates/responses/job_status.mako", + response_schemas=sd.derive_responses( + sd.get_single_job_status_responses, + sd.GenericHTMLResponse(name="HTMLJobStatus", description="Job status.") + ), +) @sd.job_service.get( tags=[sd.TAG_JOBS, sd.TAG_STATUS], schema=sd.GetJobEndpoint(), @@ -349,7 +379,7 @@ def trigger_job_execution(request): ) @log_unhandled_exceptions(logger=LOGGER, message=sd.InternalServerErrorResponseSchema.description) def get_job_status(request): - # type: (PyramidRequest) -> HTTPOk + # type: (PyramidRequest) -> AnyViewResponse """ Retrieve the status of a job. """ @@ -358,7 +388,9 @@ def get_job_status(request): schema, headers = get_job_status_schema(request) if schema == JobStatusSchema.OPENEO: job_body["status"] = map_status(job_body["status"], StatusCompliant.OPENEO) - return HTTPOk(json=job_body, headers=headers) + if ContentType.APP_JSON in str(headers.get("Content-Type")): + return HTTPOk(json=job_body, headers=headers) + return Box(**job_body, job=job, box_intact_types=[Job]) @sd.provider_job_service.patch( diff --git a/weaver/wps_restapi/jobs/utils.py b/weaver/wps_restapi/jobs/utils.py index 6cbf6fbea..eda93819c 100644 --- a/weaver/wps_restapi/jobs/utils.py +++ b/weaver/wps_restapi/jobs/utils.py @@ -333,10 +333,8 @@ def get_job_status_schema(request): def make_headers(resolved_schema): # type: (JobStatusSchemaType) -> HeadersType content_type = clean_media_type_format(content_accept.split(",")[0], strip_parameters=True) - # FIXME: support HTML or XML - # (allow transparently for browsers types since Accept did not raise earlier, and no other supported yet) if content_type in ContentType.ANY_XML | {ContentType.TEXT_HTML}: - content_type = ContentType.APP_JSON + return {"Content-Type": content_type} content_profile = f"{content_type}; profile={resolved_schema}" content_headers = {"Content-Type": content_profile} if resolved_schema == JobStatusSchema.OGC: @@ -356,7 +354,9 @@ def make_headers(resolved_schema): return schema, headers ctype = get_header("Accept", request.headers) if not ctype: - return JobStatusSchema.OGC, {} + schema = JobStatusSchema.OGC + headers = make_headers(schema) + return schema, headers params = parse_kvp(ctype) profile = params.get("profile") if not profile: diff --git a/weaver/wps_restapi/templates/responses/job_status.mako b/weaver/wps_restapi/templates/responses/job_status.mako new file mode 100644 index 000000000..c066a0173 --- /dev/null +++ b/weaver/wps_restapi/templates/responses/job_status.mako @@ -0,0 +1,163 @@ +<%inherit file="weaver.wps_restapi:templates/responses/base.mako"/> +<%namespace name="util" file="weaver.wps_restapi:templates/responses/util.mako"/> + +<%block name="breadcrumbs"> +
  • Home
  • +
  • Jobs
  • +
  • Job [${job.id}]
  • + + +

    + Job Status +

    + + + +
    + + + +
    +

    + Job Metadata +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + %for field in ["created", "started", "updated", "finished"]: + + + + + %endfor +
    Job ID${job.id}
    Process ID + ${job.process} +
    Provider ID + %if job.service: + ${job.service} + %else: + n/a + %endif +
    Status + ${util.render_status(status)} +
    Message + ${message} +
    Progress + ${util.render_progress(job.progress, job.status)} +
    Duration + ${job.duration} +
    ${field.capitalize()} + %if job.get(field): + ${job.get(field)} + %else: + n/a + %endif +
    +
    + +
    +

    + Job Results +

    + +
    + +
    +

    + Job Logs +

    + +
    + +
    +

    + Job Statistics +

    + +
    + +
    +

    + Job Logs +

    + +
    + +
    +

    + Job Provenance +

    + +
    + +
    diff --git a/weaver/wps_restapi/templates/static/style.css b/weaver/wps_restapi/templates/static/style.css index 74d83cbf7..a7f7bb1c2 100644 --- a/weaver/wps_restapi/templates/static/style.css +++ b/weaver/wps_restapi/templates/static/style.css @@ -168,6 +168,7 @@ body { margin-left: 1em; } +.job-title > .code, .process-title .code { font-size: 130%; } @@ -191,7 +192,17 @@ body { padding: 1em; } -.table-jobs-field { +.table-job-status, +.table-job-status th, +.table-job-status td { + border-style: solid; + border-width: 1px; + border-collapse: collapse; + padding: 1em; +} + +.table-jobs-field, +.table-job-status-field { font-size: 90%; text-align: justify; }