Skip to content
Open
Show file tree
Hide file tree
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
15 changes: 5 additions & 10 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Build, test, and check that the docs still build, on every push and pull request.
# Code and docs ship together: a change that breaks the docs (a shipped screen without help, an
# API call missing from the pinned spec, a broken link) fails here.
# Build, test, lint, and check every API call the app's screens make against the pinned spec,
# on every push and pull request. Help pages live in the shared Hub help center
# (substratalapps-applications-specs); help-status.yml keeps this app's status there current.
name: Android

on:
Expand Down Expand Up @@ -43,11 +43,6 @@ jobs:
- uses: actions/setup-python@v7
with:
python-version: "3.12"
cache: pip
cache-dependency-path: site/requirements.txt

- name: Docs toolchain
run: python -m venv site/.venv && site/.venv/bin/pip install -r site/requirements.txt

- name: API coverage, docs generation, strict docs build
run: make docs
- name: Every API call in the screen catalog exists in the pinned spec
run: pip install pyyaml && make api-coverage
85 changes: 0 additions & 85 deletions .github/workflows/docs.yml

This file was deleted.

69 changes: 69 additions & 0 deletions .github/workflows/help-status.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Keeps this app's entry in the Substratal Hub help center current.
# On every merge to main it regenerates help/apps/android.json (which screens the app has built)
# and, if it changed, opens or updates a pull request in substratalapps-applications-specs. Then it
# sends the help center an app-docs-updated dispatch so it rebuilds.
#
# Needs the secret SPECS_DOCS_TOKEN: a fine-grained token with Contents and Pull requests
# read/write on CompositeCode/substratalapps-applications-specs (see that repo's help/README.md).
name: Help status

on:
push:
branches: [main]
workflow_dispatch:

concurrency:
group: help-status
cancel-in-progress: false

env:
SPECS_REPO: CompositeCode/substratalapps-applications-specs
BRANCH: help-status/android

jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- uses: actions/setup-java@v6
with:
distribution: temurin
java-version: "21"

- uses: gradle/actions/setup-gradle@v6

- name: Export build status
run: make help-status

- name: Open or update the help center pull request
env:
GH_TOKEN: ${{ secrets.SPECS_DOCS_TOKEN }}
run: |
set -euo pipefail
gh repo clone "$SPECS_REPO" specs -- --depth 1
cd specs
mkdir -p help/apps
if cmp -s ../build/help/android.json help/apps/android.json; then
echo "help/apps/android.json is already current."
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git switch -c "$BRANCH"
cp ../build/help/android.json help/apps/android.json
git add help/apps/android.json
git commit -m "Android build status from substratalapps-android@${GITHUB_SHA::7}"
git push --force origin "$BRANCH"
if ! gh pr view "$BRANCH" --repo "$SPECS_REPO" >/dev/null 2>&1; then
gh pr create --repo "$SPECS_REPO" --head "$BRANCH" --base main \
--title "Android build status" \
--body "Generated from CompositeCode/substratalapps-android@${GITHUB_SHA} by its help-status workflow. Updates which screens the Android app has built, shown on each screen page's **On your platform** table."
fi

- name: Tell the help center to rebuild
env:
GH_TOKEN: ${{ secrets.SPECS_DOCS_TOKEN }}
run: |
gh api "repos/$SPECS_REPO/dispatches" -f event_type=app-docs-updated \
-F 'client_payload[app]=android' -F "client_payload[sha]=$GITHUB_SHA"
8 changes: 0 additions & 8 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,5 @@ captures/
!debug.keystore
google-services.json

# Docs site
site/.venv/
site/build/
site/.generated/
# Generated by site/scripts/generate.py from the app and openapi/ (never edit by hand)
site/docs/reference/screens/
site/docs/developers/api/v*/

# OS
.DS_Store
25 changes: 6 additions & 19 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
# Common tasks. The Android build itself is plain Gradle (./gradlew …).
PY ?= site/.venv/bin/python
MKDOCS ?= site/.venv/bin/mkdocs
GRADLE ?= ./gradlew

.PHONY: build test check install install-local docs-deps catalog docs docs-serve api-coverage
.PHONY: build test check install install-local help-status api-coverage

build: ## Debug APK
$(GRADLE) assembleDebug
Expand All @@ -17,21 +15,10 @@ install: ## Install on a connected device (production API)
install-local: ## Install, pointed at the API devserver on the emulator's host
$(GRADLE) installDebug -Psubstratal.apiEnvironment=LOCAL

docs-deps: ## Create the docs virtualenv
python3 -m venv site/.venv && site/.venv/bin/pip install -q -r site/requirements.txt
help-status: ## Write build/help/screens.json and build/help/android.json (help center status)
$(GRADLE) -q :core:model:exportHelp

catalog: ## Export the app's screen catalog for the docs
$(GRADLE) -q :core:model:exportScreenCatalog
api-coverage: help-status ## Every API call a screen makes must exist in the pinned spec (needs PyYAML)
python3 scripts/check_api_coverage.py build/help/screens.json openapi/v1/openapi.yaml

api-coverage: catalog ## Every API call a screen makes must exist in the pinned spec
$(PY) scripts/check_api_coverage.py site/.generated/screens.json openapi/v1/openapi.yaml

docs: api-coverage ## Generate and build the docs site (strict)
$(PY) site/scripts/generate.py
cd site && .venv/bin/mkdocs build --strict

docs-serve: api-coverage ## Generate and serve the docs at http://127.0.0.1:8000
$(PY) site/scripts/generate.py
cd site && .venv/bin/mkdocs serve

check: test docs ## Everything CI runs
check: test api-coverage ## Everything CI runs, apart from lint
16 changes: 6 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

The Android management app for the [Substratal Apps Platform API](https://compositecode.github.io/substratalapps.com/). It's the Android build of the Substratal Hub defined in [substratalapps-applications-specs](https://github.com/CompositeCode/substratalapps-applications-specs), built admin-first, plus a new Operations section for monitoring the API.

It also hosts **Substratal Help** (`site/`), the documentation site the management apps on every platform link to.
Help pages live in the **Substratal Hub help center**, shared by every Hub app: <https://compositecode.github.io/substratalapps-applications-specs/help/> (source: `help/` in [substratalapps-applications-specs](https://github.com/CompositeCode/substratalapps-applications-specs)). Every screen's **?** button opens its page there.

## API versioning: no `/v1` in the URL

Expand Down Expand Up @@ -31,7 +31,6 @@ core/network/ OkHttp/Retrofit client: version header, auth, single-flight
core/designsystem/ Substratal themes (5 light, 5 dark), brand assets, status badges, components
core/data/ Session + Keystore-encrypted token store, capabilities, preferences
openapi/v1/ Pinned openapi.yaml + where it came from; CHANGELOG.md lists every sync
site/ Substratal Help (MkDocs Material + mike + Swagger UI)
scripts/ API coverage check, spec sync
docs/ Planning documents
```
Expand All @@ -55,15 +54,12 @@ make up && make migrate && make seed && make run && make quickstart

Then sign in as `admin@example.com` with the password `local admin passphrase`.

## Docs
## Help and documentation

```bash
make docs-deps # once: Python venv with the pinned toolchain
make docs-serve # http://127.0.0.1:8000
make docs # strict build, as CI runs it
```

What's generated, what CI enforces, and how publishing and API-spec syncing work are covered in [site/docs/developers/docs-workflow.md](site/docs/developers/docs-workflow.md).
- **Help links.** `HelpLinks` opens `screens/<id>.html` in the help center for design-package screens. Screens this app adds on its own (the `OPS-*` preview) open the help home, because the spec's `OPS-*` IDs are different web-only Console screens.
- **Build status.** `make help-status` writes `build/help/android.json`, listing the screens this app has built. On every merge to `main`, `.github/workflows/help-status.yml` opens or updates a PR putting it at `help/apps/android.json` in the specs repo, then asks the help center to rebuild. Each screen's help page then shows "Android: Available in 0.1.0". It needs a `SPECS_DOCS_TOKEN` secret.
- **API contract.** `openapi/v1/` pins the spec this build is checked against. `make api-coverage` fails if a screen calls an operation that isn't in it, and `api-spec-sync.yml` opens a PR here when the API's spec changes.
- **Adding a screen.** Register it in `implementedScreens` and `ImplementationStatus`; unit tests fail if they disagree. Write its usage notes in the help center's `help/screens/<id>.md`.

## Status

Expand Down
12 changes: 3 additions & 9 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@ plugins {
alias(libs.plugins.hilt)
}

// The docs site publishes one version per app minor version (mike), so in-app help links to the
// documentation that matches this build. Debug builds link to the "dev" docs.
val appVersionName = "0.1.0"
val docsVersion = appVersionName.split(".").take(2).joinToString(".")
val appVersionName = providers.gradleProperty("substratal.appVersion").get()
// In-app help opens the Substratal Hub help center that every Hub app shares.
val helpBaseUrl = providers.gradleProperty("substratal.helpBaseUrl")
.orElse("https://compositecode.github.io/substratalapps-android/").get()
.orElse("https://compositecode.github.io/substratalapps-applications-specs/help/").get()
val apiEnvironment = providers.gradleProperty("substratal.apiEnvironment").orElse("PRODUCTION").get()

android {
Expand All @@ -29,14 +27,10 @@ android {
}

buildTypes {
debug {
buildConfigField("String", "DOCS_VERSION", "\"dev\"")
}
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
buildConfigField("String", "DOCS_VERSION", "\"$docsVersion\"")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ fun SignInScreen(viewModel: AuthViewModel = hiltViewModel()) {
Button(onClick = { viewModel.signIn(email, password) }, enabled = canSubmit, modifier = Modifier.fillMaxWidth()) {
if (ui.busy) CircularProgressIndicator(Modifier.size(18.dp), strokeWidth = 2.dp) else Text("Sign in")
}
TextButton(onClick = { HelpLinks.open(context, HelpLinks.page("getting-started/sign-in/")) }) {
TextButton(onClick = { HelpLinks.open(context, HelpLinks.page("guides/sign-in-and-security.html")) }) {
Text("Forgot password or need help signing in?")
}
}
Expand Down
32 changes: 17 additions & 15 deletions app/src/main/kotlin/com/substratalapps/hub/help/HelpLinks.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,18 @@ import com.substratalapps.hub.BuildConfig
import com.substratalapps.hub.model.ScreenSpec

/**
* Links into the Substratal documentation site. URLs are versioned like the site:
* `{base}{docsVersion}/{path}`, where a release build's docs version is its minor version and
* debug builds use `dev`. Every platform's app uses the same paths, so a screen id is a stable
* help address on all of them (`/reference/screens/usr-01/`).
* Links into the Substratal Hub help center, the documentation every Hub app shares
* (substratalapps-applications-specs, `help/`). A screen's page is `screens/<id>.html` on every
* platform; see the help center's "Linking to help from an app".
*/
object HelpLinks {
private val root get() = "${BuildConfig.HELP_BASE_URL}${BuildConfig.DOCS_VERSION}/"
private val root get() = BuildConfig.HELP_BASE_URL

fun home() = root
fun forScreen(screen: ScreenSpec) = root + screen.docsPath

/** The screen's help page; screens this app adds outside the design package open the help home. */
fun forScreen(screen: ScreenSpec) = screen.helpPath?.let { root + it } ?: home()

fun page(path: String) = root + path.trimStart('/')

fun open(context: Context, url: String) {
Expand All @@ -28,13 +30,13 @@ object HelpLinks {
data class HelpTopic(val title: String, val summary: String, val path: String)

val helpTopics = listOf(
HelpTopic("Getting started", "Install the app, sign in and find your way around.", "getting-started/"),
HelpTopic("Tutorials", "Step-by-step: invite a user, turn access off, register an app, create a key.", "tutorials/"),
HelpTopic("Managing users", "Search, invite, suspend and sign people out.", "guides/users/"),
HelpTopic("Access and roles", "Turn apps on and off for people, and decide what they can do.", "guides/access-and-roles/"),
HelpTopic("Applications and keys", "Register applications, manage API keys, webhooks and push.", "guides/applications/"),
HelpTopic("Operations", "Monitor the API: health, errors, usage, alerts and cost.", "guides/operations/"),
HelpTopic("Screen reference", "Every screen, what it does and the API calls behind it.", "reference/screens/"),
HelpTopic("API reference", "OpenAPI / Swagger reference for the Platform API, by version.", "developers/api/"),
HelpTopic("FAQ", "Answers to common questions.", "faq/"),
HelpTopic("Getting started", "What the Hub is, how it's organized, and where things are.", "guides/getting-started.html"),
HelpTopic("The Android app", "Layouts, links, offline use and security on Android.", "guides/android-app.html"),
HelpTopic("Your first hour as an administrator", "A guided tour of the tasks staff do first.", "tutorials/first-hour-as-an-administrator.html"),
HelpTopic("Users and access", "Find people, invite them, and turn app access on and off.", "guides/users-and-access.html"),
HelpTopic("Roles and permissions", "What each role can do, and how to change it.", "guides/roles-and-permissions.html"),
HelpTopic("Applications", "Register and manage applications, keys and webhooks.", "guides/applications.html"),
HelpTopic("Every screen", "Each screen, who can use it, and the API calls behind it.", "screens/index.html"),
HelpTopic("API reference", "Interactive OpenAPI reference for the Platform API, by version.", "api/index.html"),
HelpTopic("FAQ", "Answers to common questions.", "faq.html"),
)
13 changes: 7 additions & 6 deletions core/model/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@ dependencies {
testImplementation(libs.junit)
}

// `./gradlew :core:model:exportScreenCatalog` writes the screen catalog as JSON for the docs site.
// `./gradlew :core:model:exportHelp` writes build/help/screens.json (for the API coverage check) and
// build/help/android.json (this app's build status for the Substratal Hub help center).
application { mainClass.set("com.substratalapps.hub.model.ExportScreenCatalogKt") }

tasks.register<JavaExec>("exportScreenCatalog") {
tasks.register<JavaExec>("exportHelp") {
group = "documentation"
description = "Writes the screen catalog to site/.generated/screens.json"
description = "Writes the screen catalog and the help center's build-status file to build/help/"
classpath = sourceSets["main"].runtimeClasspath
mainClass.set("com.substratalapps.hub.model.ExportScreenCatalogKt")
val out = rootProject.layout.projectDirectory.file("site/.generated/screens.json").asFile
args(out.absolutePath)
outputs.file(out)
val out = rootProject.layout.buildDirectory.dir("help").get().asFile
args(out.absolutePath, providers.gradleProperty("substratal.appVersion").get())
outputs.dir(out)
}
Loading
Loading