Skip to content

Commit

Permalink
Merge pull request #119 from lsst-sqre/u/jsickcodes/builtin-dashboard
Browse files Browse the repository at this point in the history
Implement dashboard generation from built-in templates
  • Loading branch information
jonathansick committed Jun 25, 2022
2 parents c75ff65 + 2d8dc8b commit a027c32
Show file tree
Hide file tree
Showing 20 changed files with 991 additions and 24 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ pgdb/

integration_tests/ltd_keeper_doc_examples.txt

# Dashboard development
dashboard_dev

# Kubernetes deployment
kubernetes/cloudsql-secrets.yaml
kubernetes/keeper-secrets.yaml
Expand Down
1 change: 1 addition & 0 deletions keeper/dashboard/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Domain for edition dashboards."""
196 changes: 196 additions & 0 deletions keeper/dashboard/context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
"""Generate Jinja template rendering context from domain models."""

from __future__ import annotations

from collections import UserList
from dataclasses import dataclass
from datetime import datetime
from typing import List, Optional, Sequence

from keeper.models import Build, Edition, EditionKind, Product


@dataclass
class ProjectContext:
"""Template context model for a project."""

title: str
"""Project title."""

source_repo_url: str
"""Url of the associated GitHub repository."""

url: str
"""Root URL where this project is published."""

@classmethod
def from_product(cls, product: Product) -> ProjectContext:
return cls(
title=product.title,
source_repo_url=product.doc_repo,
url=product.published_url,
)


@dataclass
class EditionContext:
"""Template context model for an edition."""

title: str
"""Human-readable label for this edition."""

url: str
"""URL where this edition is published."""

date_updated: datetime
"""Date when this edition was last updated."""

kind: EditionKind
"""The edition's kind."""

slug: str
"""The edition's slug."""

git_ref: Optional[str]
"""The git ref that this edition tracks."""

github_url: Optional[str]
"""URL to this git ref on GitHub."""

@classmethod
def from_edition(
cls, edition: Edition, product: Product
) -> EditionContext:
if edition.tracked_ref and product.doc_repo:
repo_url = product.doc_repo.rstrip("/")
if repo_url[-4:] == ".git":
repo_url = repo_url[:-4]
github_url = f"{repo_url}/tree/{edition.tracked_ref}"
else:
github_url = None

return cls(
title=edition.title,
url=edition.published_url,
date_updated=edition.date_rebuilt,
kind=edition.kind,
slug=edition.slug,
git_ref=edition.tracked_ref,
github_url=github_url,
)


class EditionContextList(UserList):
def __init__(self, contexts: Sequence[EditionContext]) -> None:
self.data: List[EditionContext] = list(contexts)
self.data.sort(key=lambda x: x.date_updated)

@property
def main_edition(self) -> EditionContext:
"""The main (current) edition."""
for edition in self.data:
if edition.slug == "__main":
return edition
raise ValueError("No __main edition found")

@property
def has_releases(self) -> bool:
return len(self.releases) > 0

@property
def releases(self) -> List[EditionContext]:
"""All editions tagged as releases."""
release_kinds = (
EditionKind.release,
EditionKind.major,
EditionKind.minor,
)
release_items = [
e
for e in self.data
if (e.kind in release_kinds and e.slug != "__main")
]
sorted_items = sorted(
release_items, key=lambda x: x.slug, reverse=True
)
return sorted_items

@property
def has_drafts(self) -> bool:
return len(self.drafts) > 0

@property
def drafts(self) -> List[EditionContext]:
"""All editions tagged as drafts."""
draft_items = [
e
for e in self.data
if (e.kind == EditionKind.draft and e.slug != "__main")
]
return sorted(draft_items, key=lambda x: x.date_updated, reverse=True)


@dataclass
class BuildContext:
"""Template context model for a build."""

slug: str
"""The URL slug for this build."""

url: str
"""The URL for this build."""

git_ref: Optional[str]
"""The git ref associated with this build (if appropriate."""

date: datetime
"""Date when the build was uploaded."""

@classmethod
def from_build(cls, build: Build) -> BuildContext:
return cls(
slug=build.slug,
url=build.published_url,
git_ref=build.git_ref,
date=build.date_created,
)


class BuildContextList(UserList):
def __init__(self, contexts: Sequence[BuildContext]) -> None:
self.data: List[BuildContext] = list(contexts)
self.data.sort(key=lambda x: x.date)


@dataclass
class Context:
"""A class that creates Jinja template rendering context from
domain models.
"""

project_context: ProjectContext

edition_contexts: EditionContextList

build_contexts: BuildContextList

@classmethod
def create(cls, product: Product) -> Context:
project_context = ProjectContext.from_product(product)

edition_contexts: EditionContextList = EditionContextList(
[
EditionContext.from_edition(edition=edition, product=product)
for edition in product.editions
]
)

build_contexts: BuildContextList = BuildContextList(
[BuildContext.from_build(build) for build in product.builds]
)

return cls(
project_context=project_context,
edition_contexts=edition_contexts,
build_contexts=build_contexts,
)
12 changes: 12 additions & 0 deletions keeper/dashboard/jinjafilters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Filters for Jinja2 templates."""

from __future__ import annotations

__all__ = ["filter_simple_date"]

from datetime import datetime


def filter_simple_date(value: datetime) -> str:
"""Filter a `datetime.datetime` into a 'YYYY-MM-DD' string."""
return value.strftime("%Y-%m-%d")
96 changes: 96 additions & 0 deletions keeper/dashboard/static/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/* Resets */
*,
*::before,
*::after {
box-sizing: border-box;
}

* {
margin: 0;
}

html,
body {
height: 100%;
}

body {
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}

img,
picture,
video,
canvas,
svg {
display: block;
max-width: 100%;
}

input,
button,
textarea,
select {
font: inherit;
}

p,
h1,
h2,
h3,
h4,
h5,
h6 {
overflow-wrap: break-word;
}

/* System font stack */
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;
}

main {
/* max-width: 16rem; */
width: 100vw;
margin: 0 auto;
padding: 1rem;
}

@media (min-width: 62rem) {
main {
width: 62rem;
}
}

.main-edition-section {
margin-top: 1rem;
}

.main-edition-section__url {
font-size: 1.2rem;
margin-bottom: 0.5rem;
}

.version-section {
margin-top: 2rem;
}

.version-section__listing {
list-style: none;
padding-left: 0;
}

.version-section__listing > li {
margin-top: 1rem;
}

.dashboard-item-metadata {
list-style: none;
display: flex;
flex-direction: row;
align-items: baseline;
gap: 1.2rem;
padding-left: 0;
}
17 changes: 17 additions & 0 deletions keeper/dashboard/template/base.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">

<head>
<meta charset="utf-8">
<title>{% block page_title %}{% endblock page_title %}</title>
<meta name="description" content="{% block page_description %}{% endblock page_description %}">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="{{ asset_dir }}/app.css">
</head>

<body>
{% block body %}
{% endblock body %}
</body>

</html>
17 changes: 17 additions & 0 deletions keeper/dashboard/template/build_dashboard.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{% extends "base.jinja" %}

{% block page_title %}{{ project.title }} builds{% endblock page_title %}
{% block page_description %}Find documentation builds.{% endblock page_description %}

{% block body %}
<main>
<header>
<h1>{{ project.title }} builds</h1>
</header>
<section>
<header>
<h2>Builds</h2>
</header>
</section>
</main>
{% endblock body %}
Loading

0 comments on commit a027c32

Please sign in to comment.