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

Rewrite learning uploader #1421

Merged
merged 75 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
e616d60
First draft: Can update lessons and add new ones! :tada:
frankharkins May 22, 2024
008d816
Use token from environment
frankharkins May 23, 2024
5d692bc
Separate API and read from YAML
frankharkins May 23, 2024
123f362
Add tutorial data
frankharkins May 23, 2024
1ae0fbd
`notebook_path` -> `local_path`
frankharkins May 23, 2024
1d4217b
Zip tutorial folders
frankharkins May 23, 2024
bbee2ca
Document developement setup
frankharkins May 23, 2024
2e3069d
Validate config file at runtime
frankharkins May 23, 2024
9f2d123
Validate `learning-api.conf.yaml`
frankharkins May 23, 2024
12bfe65
Handle topics
frankharkins May 23, 2024
f9f9eef
Refactor: De-duplicate `getId` functionality
frankharkins May 24, 2024
ce6adbb
Refactor: Generalize getting many IDs
frankharkins May 24, 2024
4a25d7b
Cleanup / lint
frankharkins May 24, 2024
8de3579
Add API tests
frankharkins May 24, 2024
f6ea3b1
Fix bug: Categories not updated (thanks tests!)
frankharkins May 24, 2024
cab6ded
Clean up temp file afterwards
frankharkins May 24, 2024
1e4d612
Improve typing a little
frankharkins May 24, 2024
0739ad9
Prettier :sparkles:
frankharkins May 24, 2024
14443d9
Fix types :tada:
frankharkins May 24, 2024
4d44d7f
prettier
frankharkins May 24, 2024
86a25ee
Commit `package-lock.json`
frankharkins May 24, 2024
a5688fa
prettier
frankharkins May 24, 2024
db4b884
Fix CI (?)
frankharkins May 24, 2024
cec2803
prettier
frankharkins May 24, 2024
ee71757
Refactor: Add types, docstrings, and clarify variable names
frankharkins May 28, 2024
8c203ff
Use `--testPathIgnorePatterns` rather than env var
frankharkins May 29, 2024
b339c18
Rename `validate.ts` -> `validate-config.ts`
frankharkins May 29, 2024
b14fa81
Update scripts/tutorial-uploader/lib/schema.ts
frankharkins May 29, 2024
a6325a8
Update scripts/tutorial-uploader/lib/api.test.ts
frankharkins May 29, 2024
5b22525
Update scripts/tutorial-uploader/lib/api.ts
frankharkins May 29, 2024
f4530d6
Merge branch 'FH/new-learning-uploader' of https://github.com/Qiskit/…
frankharkins May 29, 2024
cbf8507
Add SO attribution
frankharkins May 29, 2024
5b11d61
Comment API quirks
frankharkins May 29, 2024
6512ac5
Use lodash `isType` functions
frankharkins May 29, 2024
1696a8b
Drop assumption that only translation is en-US
frankharkins May 29, 2024
ad843ec
Use single mock tutorial object
frankharkins May 29, 2024
a3badb3
Explain why `cd`
frankharkins May 29, 2024
1a1b7e9
lint
frankharkins May 29, 2024
99524a1
Make helper function `getTutorialIdBySlug`
frankharkins May 29, 2024
02e3ee9
Improve `getEnglishTranslationId`
frankharkins May 29, 2024
0e3e5dc
Fix/ignore TS warnings
frankharkins May 29, 2024
d5e32c2
Prettier
frankharkins May 29, 2024
9135b90
Ignore test notebook
frankharkins May 29, 2024
2e03fde
Improve logging
frankharkins May 29, 2024
a138d42
Update tutorial documentation
frankharkins May 29, 2024
e85dade
Update workflow
frankharkins May 29, 2024
a1980b7
Remove staging URL
frankharkins May 29, 2024
7088cf6
Verify environment is set correctly
frankharkins May 29, 2024
a1095a1
Set up topics and categories automatically
frankharkins May 29, 2024
e1e17ca
Cleanup
frankharkins May 29, 2024
7544353
Merge branch 'main' of https://github.com/Qiskit/documentation into F…
frankharkins May 29, 2024
f99965e
oops
frankharkins May 29, 2024
72089f2
Apply suggestions from code review
frankharkins May 30, 2024
ae35611
Assert type rather than cast
frankharkins May 30, 2024
0741e9c
Change capitalization to match learning platform
frankharkins May 30, 2024
bc5aa11
Update scripts/tutorial-uploader/lib/api.ts
frankharkins May 30, 2024
58d326b
Apply suggestions from code review
frankharkins May 30, 2024
587288d
Merge branch 'main' of https://github.com/Qiskit/documentation into F…
frankharkins Jun 7, 2024
ebcfd9a
Merge branch 'main' of https://github.com/Qiskit/documentation into F…
frankharkins Jun 10, 2024
10da9dd
Privatise methods and add comment
frankharkins Jun 10, 2024
cde2200
Merge branch 'FH/new-learning-uploader' of https://github.com/Qiskit/…
frankharkins Jun 10, 2024
e228965
Add comments reminding to update
frankharkins Jun 10, 2024
3bf7bc4
Update README
frankharkins Jun 10, 2024
208c086
Add warning to avoid changing slugs
frankharkins Jun 14, 2024
cc1cf94
Fix types
frankharkins Jun 14, 2024
2dee7ff
Fix tests
frankharkins Jun 14, 2024
92c3c6d
Add ability to set access controls
frankharkins Jul 5, 2024
db455e3
Merge branch 'main' of https://github.com/Qiskit/documentation into F…
frankharkins Jul 5, 2024
22edaf8
Add back reverted change in merge
frankharkins Jul 5, 2024
d57e725
Remove config file
frankharkins Jul 8, 2024
2bd7be4
Merge branch 'main' of https://github.com/Qiskit/documentation into F…
frankharkins Jul 8, 2024
e0d0ea7
Add learning-api.conf.yaml for final notebook
frankharkins Jul 8, 2024
b74619f
Apply suggestions from code review
frankharkins Jul 8, 2024
07a02b2
Update info (and add note about reading time)
frankharkins Jul 8, 2024
bcf10e0
Change slug
frankharkins Jul 9, 2024
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: 7 additions & 8 deletions .github/workflows/deploy-tutorials.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,14 @@ jobs:
environment: ${{ inputs.environment }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
- name: Set up Node.js
uses: actions/setup-node@v3
with:
python-version: "3.11"
- name: Install tool
run: pip install ./scripts/ibm-quantum-learning-uploader
node-version: 18
- name: Install Node.js dependencies
run: npm ci
- name: Upload tutorials
run: |
cd tutorials
sync-lessons
run: npm run tutorial:sync
env:
LEARNING_API_TOKEN: ${{ secrets.LEARNING_API_TOKEN }}
LEARNING_API_ENVIRONMENT: ${{ vars.LEARNING_API_ENVIRONMENT }}
LEARNING_API_URL: ${{ vars.LEARNING_API_URL }}
28 changes: 25 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@
"check:qiskit-bot": "node -r esbuild-register scripts/commands/checkQiskitBotFiles.ts",
"check:stale-images": "node -r esbuild-register scripts/commands/checkStaleImages.ts",
"fmt": "prettier --write .",
"test": "jest",
"test": "jest --testPathIgnorePatterns='tutorial-uploader'",
"test:tutorial-uploader": "jest scripts/tutorial-uploader",
"typecheck": "tsc",
"regen-apis": "node -r esbuild-register scripts/commands/regenerateApiDocs.ts",
"gen-api": "node -r esbuild-register scripts/commands/updateApiDocs.ts",
"make-historical": "node -r esbuild-register scripts/commands/convertApiDocsToHistorical.ts"
"make-historical": "node -r esbuild-register scripts/commands/convertApiDocsToHistorical.ts",
"tutorial:sync": "node -r esbuild-register scripts/tutorial-uploader/sync.ts",
"tutorial:validate": "node -r esbuild-register scripts/tutorial-uploader/validate-config.ts",
"tutorial:setup-testing": "node -r esbuild-register scripts/tutorial-uploader/setup-for-testing.ts"
},
"devDependencies": {
"@swc/jest": "^0.2.29",
Expand Down Expand Up @@ -60,8 +64,10 @@
"zx": "^7.2.3"
},
"dependencies": {
"@directus/sdk": "^16.0.1",
"esbuild": "^0.19.11",
"fast-levenshtein": "^3.0.0",
"js-yaml": "^4.1.0",
"markdown-link-extractor": "^3.1.0",
"transform-markdown-links": "^2.1.0"
}
Expand Down
1 change: 1 addition & 0 deletions scripts/nb-tester/notebooks.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ notebooks_normal_test = [
# Don't test the following notebooks (this section can include glob patterns)
notebooks_exclude = [
"scripts/ibm-quantum-learning-uploader/test/template.ipynb",
"scripts/tutorial-uploader/lib/test-data/simple-tutorial/notebook.ipynb",
"**/.ipynb_checkpoints/**",
# The following notebooks are broken and need fixing
"tutorials/submitting-transpiled-circuits/submitting-transpiled-circuits.ipynb",
Expand Down
40 changes: 40 additions & 0 deletions scripts/tutorial-uploader/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Tutorial uploader

This script uploads tutorials to IBM Quantum Learning.

## Developing

To work on this script, you'll need to set up the `saiba-api` project locally.

There are some extra steps you'll need to take to set up `saiba-api` for
developing this script:

- Follow the instructions in the README in the saiba-api repo. Don't forget the
instructions to add the `PUBLIC_URL` entry to your `docker-compose.yaml`.

- Login into the local CMS (<http://0.0.0.0:8055/admin/>) using

- email: `admin@example.com`
- password: `password`

- Create a token for local testing.

1. In the local CMS, go to "User directory" (in the leftmost navbar)
2. Click "Create item"
3. Create a new user with the "Content creator admin" role and generate a new
static token. Copy the token to your clipboard. Then click the tick on the
top-right of the page to save the user.
4. To test the script in this repo, export the following environment
variables.
```
export LEARNING_API_URL=http://0.0.0.0:8055
export LEARNING_API_TOKEN=<copied-token>
```
Consider using [direnv](https://direnv.net/) to handle this.

- With the local database running, run the following command to add the topics
and categories that we expect to exist.

```
npm run tutorial:setup-testing
```
154 changes: 154 additions & 0 deletions scripts/tutorial-uploader/lib/api.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// This code is a Qiskit project.
//
// (C) Copyright IBM 2024.
//
// This code is licensed under the Apache License, Version 2.0. You may
// obtain a copy of this license in the LICENSE file in the root directory
// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
//
// Any modifications or derivative works of this code must retain this
// copyright notice, and modified files need to carry a notice indicating
// that they have been altered from the originals.

import { describe, expect, test } from "@jest/globals";
import { readItem } from "@directus/sdk";

import { API } from "./api";
import { type LocalTutorialData } from "./local-tutorial-data";
frankharkins marked this conversation as resolved.
Show resolved Hide resolved

/* Create test data */
const MOCK_TUTORIAL: LocalTutorialData = {
title: "Mock tutorial for testing",
short_description: "A mock tutorial for testing with Jest",
slug: "mock-tutorial",
status: "published",
local_path: "scripts/tutorial-uploader/lib/test-data/simple-tutorial",
category: "Workflow example",
topics: [],
reading_time: 50,
catalog_featured: false,
required_instance_access: ["ibm-quantum/group/project"],
allowed_email_domains: ["ibm.com", "hotmail.co.uk"],
};

/* Just to be sure */
if (/learning-api\.quantum\.ibm\.com/.test(process.env.LEARNING_API_URL!)) {
throw new Error(
"Tried to run tests against production! Set the env var LEARNING_API_URL to either staging or local (see tutorial-uploader/README.md)",
frankharkins marked this conversation as resolved.
Show resolved Hide resolved
);
}

describe("Tutorial uploader API", () => {
const api = new API();

beforeAll(async () => {
if (await api.getTutorialIdBySlug(MOCK_TUTORIAL.slug)) {
await api.deleteTutorial(MOCK_TUTORIAL.slug);
}
});

afterEach(async () => {
await api.deleteTutorial(MOCK_TUTORIAL.slug);
});

test("upload new tutorial", async () => {
expect(await api.getTutorialIdBySlug(MOCK_TUTORIAL.slug)).toBeNull();

await api.upsertTutorial(MOCK_TUTORIAL);
const tutorialId = await api.getTutorialIdBySlug(MOCK_TUTORIAL.slug);
expect(tutorialId).toBeTruthy();

const retrievedTutorial = await api.client.request(
readItem("tutorials", tutorialId as string, {
fields: ["*", { translations: ["*"] }],
}),
);
expect(retrievedTutorial).toMatchObject({
slug: MOCK_TUTORIAL.slug,
status: MOCK_TUTORIAL.status,
reading_time: MOCK_TUTORIAL.reading_time,
catalog_featured: MOCK_TUTORIAL.catalog_featured,
category: await api.getId(
"tutorials_categories",
"name",
MOCK_TUTORIAL.category,
),
topics: [],
allowed_email_domains: ["ibm.com", "hotmail.co.uk"],
required_instance_access: ["ibm-quantum/group/project"],
sort: null,
translations: [
{
title: MOCK_TUTORIAL.title,
short_description: MOCK_TUTORIAL.short_description,
content: "Here's some basic content.\n",
languages_code: "en-US",
},
],
});
});

test("update existing tutorial", async () => {
// Upload tutorial
await api.upsertTutorial(MOCK_TUTORIAL);
const tutorialId = await api.getTutorialIdBySlug(MOCK_TUTORIAL.slug);

// Mutate tutorial data and re-upload
const modifiedTutorial = {
...MOCK_TUTORIAL,
title: "A new tutorial title",
short_description: "A modified short description",
status: "draft",
category: "How-to",
reading_time: 33,
topics: ["Scheduling", "Transpilation"],
catalog_featured: false,
};
await api.upsertTutorial(modifiedTutorial);

// Retrieve and check
const retrievedTutorial = await api.client.request(
readItem("tutorials", tutorialId as string, {
fields: ["*", { topics: ["*"] }],
}),
);
const topicIds = (await Promise.all(
modifiedTutorial.topics.map((name) =>
api.getId("tutorials_topics", "name", name),
),
)) as string[];
expect(retrievedTutorial).toMatchObject({
slug: modifiedTutorial.slug,
status: modifiedTutorial.status,
reading_time: modifiedTutorial.reading_time,
catalog_featured: modifiedTutorial.catalog_featured,
category: await api.getId(
"tutorials_categories",
"name",
modifiedTutorial.category,
),
topics: topicIds.map((id) => {
return { tutorials_topics_id: id };
}),
allowed_email_domains: ["ibm.com", "hotmail.co.uk"],
required_instance_access: ["ibm-quantum/group/project"],
sort: null,
});

const retrievedTranslation = (
await api.client.request(
readItem("tutorials", tutorialId as string, {
fields: [{ translations: ["*"] }],
}),
)
).translations;
expect(retrievedTranslation).toMatchObject([
{
title: modifiedTutorial.title,
short_description: modifiedTutorial.short_description,
content: "Here's some basic content.\n",
languages_code: "en-US",
},
]);
});
});
Loading
Loading