You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@beam.apache.org by da...@apache.org on 2022/09/08 15:36:19 UTC
[beam] branch master updated: [Tour Of Beam][backend] integration tests and GA workflow (#23032)
This is an automated email from the ASF dual-hosted git repository.
damccorm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git
The following commit(s) were added to refs/heads/master by this push:
new 9efa3787aef [Tour Of Beam][backend] integration tests and GA workflow (#23032)
9efa3787aef is described below
commit 9efa3787aefe9198c7985dd30b16691cdba61a7e
Author: Evgeny Antyshev <ea...@gmail.com>
AuthorDate: Thu Sep 8 18:36:10 2022 +0300
[Tour Of Beam][backend] integration tests and GA workflow (#23032)
* tests
* WF
* chdir
* order
* nit
* fix
* nit
* concurrency
* integration_go
* tags
* cache_go
* nit
* nit
* rat
* datadir
* env
* env
* reduce fun
* removing unicode character
* Update learning/tour-of-beam/backend/integration_tests/client.go
Co-authored-by: Danny McCormick <da...@google.com>
* Update learning/tour-of-beam/backend/integration_tests/function_test.go
Co-authored-by: Danny McCormick <da...@google.com>
* review
* nit
Co-authored-by: oborysevych <ol...@akvelon.com>
Co-authored-by: Danny McCormick <da...@google.com>
---
.github/workflows/tour_of_beam_backend.yml | 8 +-
.../workflows/tour_of_beam_backend_integration.yml | 96 +++++++++++++
build.gradle.kts | 3 +
learning/tour-of-beam/backend/docker-compose.yml | 4 +-
learning/tour-of-beam/backend/function.go | 1 +
.../tour-of-beam/backend/integration_tests/api.go | 64 +++++++++
.../backend/integration_tests/client.go | 67 ++++++++++
.../backend/integration_tests/function_test.go | 148 +++++++++++++++++++++
.../backend/integration_tests/local.sh | 55 ++++++++
.../backend/internal/fs_content/load_test.go | 6 +-
.../backend/internal/storage/image/Dockerfile | 2 -
.../backend/internal/storage/image/index.yaml | 29 ----
.../backend/internal/storage/index.yaml | 20 +++
.../backend/samples/api/get_unit_content.json | 18 +--
.../backend/samples/api/get_unit_content_full.json | 15 +++
.../java/module 1/unit-challenge/description.md | 2 +-
.../java/module 1/unit-challenge/hint1.md | 2 +-
.../java/module 1/unit-challenge/hint2.md | 2 +-
.../java/module 2/unit-challenge/description.md | 2 +-
.../java/module 2/unit-challenge/hint1.md | 2 +-
.../java/module 2/unit-challenge/hint2.md | 2 +-
.../module 1/group/unit-challenge/description.md | 2 +-
.../python/module 1/group/unit-challenge/hint1.md | 2 +-
.../python/module 1/group/unit-challenge/hint2.md | 2 +-
24 files changed, 497 insertions(+), 57 deletions(-)
diff --git a/.github/workflows/tour_of_beam_backend.yml b/.github/workflows/tour_of_beam_backend.yml
index 2c186c710c3..7182911b3c2 100644
--- a/.github/workflows/tour_of_beam_backend.yml
+++ b/.github/workflows/tour_of_beam_backend.yml
@@ -17,7 +17,7 @@
# To learn more about GitHub Actions in Apache Beam check the CI.md
-name: Tour of Beam Go tests
+name: Tour of Beam Go unittests
on:
push:
@@ -28,6 +28,11 @@ on:
tags: 'v*'
paths: ['learning/tour-of-beam/backend/**']
+# This allows a subsequently queued workflow run to interrupt previous runs
+concurrency:
+ group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'
+ cancel-in-progress: true
+
jobs:
checks:
runs-on: ubuntu-latest
@@ -38,6 +43,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
+ # pin to the biggest Go version supported by Cloud Functions runtime
go-version: '1.16'
- name: Run fmt
run: |
diff --git a/.github/workflows/tour_of_beam_backend_integration.yml b/.github/workflows/tour_of_beam_backend_integration.yml
new file mode 100644
index 00000000000..f584c7f7e00
--- /dev/null
+++ b/.github/workflows/tour_of_beam_backend_integration.yml
@@ -0,0 +1,96 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# To learn more about GitHub Actions in Apache Beam check the CI.md
+
+name: Tour of Beam Go integration tests
+
+on:
+ push:
+ branches: ['master', 'release-*']
+ tags: 'v*'
+ pull_request:
+ branches: ['master', 'release-*']
+ tags: 'v*'
+ paths: ['learning/tour-of-beam/backend/**']
+
+# This allows a subsequently queued workflow run to interrupt previous runs
+concurrency:
+ group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'
+ cancel-in-progress: true
+
+env:
+ TOB_LEARNING_ROOT: ./samples/learning-content
+ DATASTORE_PROJECT_ID: test-proj
+ DATASTORE_EMULATOR_HOST: localhost:8081
+ DATASTORE_EMULATOR_DATADIR: ./datadir
+ PORT_SDK_LIST: 8801
+ PORT_GET_CONTENT_TREE: 8802
+ PORT_GET_UNIT_CONTENT: 8803
+
+
+jobs:
+ integration:
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: ./learning/tour-of-beam/backend
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-go@v3
+ with:
+ # pin to the biggest Go version supported by Cloud Functions runtime
+ go-version: '1.16'
+
+ # 1. Datastore emulator
+ - name: 'Set up Cloud SDK'
+ uses: 'google-github-actions/setup-gcloud@v0'
+ with:
+ version: 397.0.0
+ project_id: ${{ env.DATASTORE_PROJECT_ID }}
+ install_components: 'beta,cloud-datastore-emulator'
+ - name: 'Start datastore emulator'
+ run: |
+ gcloud beta emulators datastore start \
+ --data-dir=${{ env.DATASTORE_EMULATOR_DATADIR }} \
+ --host-port=${{ env.DATASTORE_EMULATOR_HOST }} \
+ --consistency=1 &
+
+ # 2. start function-framework processes in BG
+ - name: Compile CF
+ run: go build -o ./tob_function cmd/main.go
+ - name: Run sdkList in background
+ run: PORT=${{ env.PORT_SDK_LIST }} FUNCTION_TARGET=sdkList ./tob_function &
+ - name: Run getContentTree in background
+ run: PORT=${{ env.PORT_GET_CONTENT_TREE }} FUNCTION_TARGET=getContentTree ./tob_function &
+ - name: Run getUnitContent in background
+ run: PORT=${{ env.PORT_GET_UNIT_CONTENT }} FUNCTION_TARGET=getUnitContent ./tob_function &
+
+ # 3. Load data in datastore: run CD step on samples/learning-content
+ - name: Run CI/CD to populate datastore
+ run: go run cmd/ci_cd/ci_cd.go
+
+ # 4. Check sdkList, getContentTree, getUnitContent: run integration tests
+ - name: Go integration tests
+ run: go test -v --tags integration ./integration_tests/...
+ # 5. Compare storage/datastore/index.yml VS generated
+ - name: Check index.yaml
+ run: |
+ diff -q "${{ env.DATASTORE_EMULATOR_DATADIR }}/WEB-INF/index.yaml" \
+ internal/storage/index.yaml \
+ || ( echo "index.yaml mismatch"; exit 1)
+
diff --git a/build.gradle.kts b/build.gradle.kts
index 88ded595c5b..7e6a8c9118a 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -115,6 +115,9 @@ tasks.rat {
"learning/tour-of-beam/**/unit-info.yaml",
"learning/tour-of-beam/backend/samples/**/*.md",
+ // Tour Of Beam backend autogenerated Datastore indexes
+ "learning/tour-of-beam/backend/internal/storage/index.yaml",
+
// test p8 file for SnowflakeIO
"sdks/java/io/snowflake/src/test/resources/invalid_test_rsa_key.p8",
diff --git a/learning/tour-of-beam/backend/docker-compose.yml b/learning/tour-of-beam/backend/docker-compose.yml
index b5ebcf08eaa..5205903791a 100644
--- a/learning/tour-of-beam/backend/docker-compose.yml
+++ b/learning/tour-of-beam/backend/docker-compose.yml
@@ -18,9 +18,11 @@ version: "3"
services:
datastore:
build: internal/storage/image
+ volumes:
+ - ${DATASTORE_EMULATOR_DATADIR}:/opt/data
environment:
- DATASTORE_PROJECT_ID=project-test
- DATASTORE_LISTEN_ADDRESS=0.0.0.0:8081
ports:
- "8081:8081"
- command: --no-store-on-disk --consistency=1.0
+ command: --consistency=1.0
diff --git a/learning/tour-of-beam/backend/function.go b/learning/tour-of-beam/backend/function.go
index 6c6e34bd311..126c00b5cbd 100644
--- a/learning/tour-of-beam/backend/function.go
+++ b/learning/tour-of-beam/backend/function.go
@@ -107,6 +107,7 @@ func init() {
if os.Getenv("TOB_MOCK") > "" {
svc = &service.Mock{}
} else {
+ // consumes DATASTORE_* env variables
client, err := datastore.NewClient(context.Background(), "")
if err != nil {
log.Fatalf("new datastore client: %v", err)
diff --git a/learning/tour-of-beam/backend/integration_tests/api.go b/learning/tour-of-beam/backend/integration_tests/api.go
new file mode 100644
index 00000000000..f8bb8b38aef
--- /dev/null
+++ b/learning/tour-of-beam/backend/integration_tests/api.go
@@ -0,0 +1,64 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+// Reduced version of API, for integration tests and documentation
+// * No hidden fields
+// * Internal enumerations: sdk, node.type to string params
+
+type sdkListResponse struct {
+ Names []string
+}
+
+type Unit struct {
+ Id string `json:"unitId"`
+ Name string `json:"name"`
+
+ // optional
+ Description string `json:"description,omitempty"`
+ Hints []string `json:"hints,omitempty"`
+ TaskSnippetId string `json:"taskSnippetId,omitempty"`
+ SolutionSnippetId string `json:"solutionSnippetId,omitempty"`
+
+ // optional, user-specific
+ UserSnippetId string `json:"userSnippetId,omitempty"`
+ IsCompleted bool `json:"isCompleted,omitempty"`
+}
+
+type Group struct {
+ Name string `json:"name"`
+ Nodes []Node `json:"nodes"`
+}
+
+type Node struct {
+ Type string `json:"type"`
+ Group *Group `json:"group,omitempty"`
+ Unit *Unit `json:"unit,omitempty"`
+}
+
+type Module struct {
+ Id string `json:"moduleId"`
+ Name string `json:"name"`
+ Complexity string `json:"complexity"`
+ Nodes []Node `json:"nodes"`
+}
+
+type ContentTree struct {
+ Sdk string `json:"sdk"`
+ Modules []Module `json:"modules"`
+}
+
+type ErrorResponse struct {
+ Code string `json:"code"`
+ Message string `json:"message,omitempty"`
+}
diff --git a/learning/tour-of-beam/backend/integration_tests/client.go b/learning/tour-of-beam/backend/integration_tests/client.go
new file mode 100644
index 00000000000..c66ab779527
--- /dev/null
+++ b/learning/tour-of-beam/backend/integration_tests/client.go
@@ -0,0 +1,67 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "encoding/json"
+ "io"
+ "net/http"
+ "os"
+)
+
+func SdkList(url string) (sdkListResponse, error) {
+ var result sdkListResponse
+ err := Get(&result, url, nil)
+ return result, err
+}
+
+func GetContentTree(url, sdk string) (ContentTree, error) {
+ var result ContentTree
+ err := Get(&result, url, map[string]string{"sdk": sdk})
+ return result, err
+}
+
+func GetUnitContent(url, sdk, unitId string) (Unit, error) {
+ var result Unit
+ err := Get(&result, url, map[string]string{"sdk": sdk, "unitId": unitId})
+ return result, err
+}
+
+// Generic HTTP call wrapper
+// params:
+// * dst: response struct pointer
+// * url: request url
+// * query_params: url query params, as a map (we don't use multiple-valued params)
+func Get(dst interface{}, url string, queryParams map[string]string) error {
+ req, err := http.NewRequest(http.MethodGet, url, nil)
+ if err != nil {
+ return err
+ }
+ req.Header.Add("Content-Type", "application/json")
+ if len(queryParams) > 0 {
+ q := req.URL.Query()
+ for k, v := range queryParams {
+ q.Add(k, v)
+ }
+ req.URL.RawQuery = q.Encode()
+ }
+ resp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ return err
+ }
+
+ defer resp.Body.Close()
+
+ tee := io.TeeReader(resp.Body, os.Stdout)
+ return json.NewDecoder(tee).Decode(dst)
+}
diff --git a/learning/tour-of-beam/backend/integration_tests/function_test.go b/learning/tour-of-beam/backend/integration_tests/function_test.go
new file mode 100644
index 00000000000..92ced75f5e7
--- /dev/null
+++ b/learning/tour-of-beam/backend/integration_tests/function_test.go
@@ -0,0 +1,148 @@
+//go:build integration
+// +build integration
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "encoding/json"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+const (
+ PORT_SDK_LIST = "PORT_SDK_LIST"
+ PORT_GET_CONTENT_TREE = "PORT_GET_CONTENT_TREE"
+ PORT_GET_UNIT_CONTENT = "PORT_GET_UNIT_CONTENT"
+)
+
+// scenarios:
+// + Get SDK list
+// + Get content tree for existing SDK
+// + Get content tree for non-existing SDK: 404 Not Found
+// + Get unit content for existing SDK, existing unitId
+// + Get unit content for non-existing SDK/unitId: 404 Not Found
+// TODO:
+// - Get content tree for a registered user
+// - Get unit content for a registered user
+// - Save user code/progress for a registered user
+// - (negative) Save user code/progress w/o user token/bad token
+// - (negative) Save user code/progress for non-existing SDK/unitId: 404 Not Found
+
+func loadJson(path string, dst interface{}) error {
+ fh, err := os.Open(path)
+ if err != nil {
+ return err
+ }
+ return json.NewDecoder(fh).Decode(dst)
+}
+
+func TestSdkList(t *testing.T) {
+ port := os.Getenv(PORT_SDK_LIST)
+ if port == "" {
+ t.Fatal(PORT_SDK_LIST, "env not set")
+ }
+ url := "http://localhost:" + port
+ exp := sdkListResponse{
+ Names: []string{"Java", "Python", "Go"},
+ }
+
+ resp, err := SdkList(url)
+ if err != nil {
+ t.Fatal(err)
+ }
+ assert.Equal(t, exp, resp)
+}
+
+func TestGetContentTree(t *testing.T) {
+ port := os.Getenv(PORT_GET_CONTENT_TREE)
+ if port == "" {
+ t.Fatal(PORT_GET_CONTENT_TREE, "env not set")
+ }
+ url := "http://localhost:" + port
+
+ mock_path := filepath.Join("..", "samples", "api", "get_content_tree.json")
+ var exp ContentTree
+ if err := loadJson(mock_path, &exp); err != nil {
+ t.Fatal(err)
+ }
+
+ resp, err := GetContentTree(url, "Python")
+ if err != nil {
+ t.Fatal(err)
+ }
+ assert.Equal(t, exp, resp)
+}
+
+func TestGetUnitContent(t *testing.T) {
+ port := os.Getenv(PORT_GET_UNIT_CONTENT)
+ if port == "" {
+ t.Fatal(PORT_GET_UNIT_CONTENT, "env not set")
+ }
+ url := "http://localhost:" + port
+
+ mock_path := filepath.Join("..", "samples", "api", "get_unit_content.json")
+ var exp Unit
+ if err := loadJson(mock_path, &exp); err != nil {
+ t.Fatal(err)
+ }
+
+ resp, err := GetUnitContent(url, "Python", "challenge1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ assert.Equal(t, exp, resp)
+}
+
+func TestNegative(t *testing.T) {
+ for i, params := range []struct {
+ portEnvName string
+ queryParams map[string]string
+ expected ErrorResponse
+ }{
+ {PORT_GET_CONTENT_TREE, nil,
+ ErrorResponse{
+ Code: "BAD_FORMAT",
+ Message: "Sdk not in: [Java Python Go SCIO]",
+ },
+ },
+ {PORT_GET_CONTENT_TREE, map[string]string{"sdk": "SCIO"},
+ // TODO: actually here should be a NOT_FOUND error
+ ErrorResponse{Code: "INTERNAL_ERROR", Message: "storage error"},
+ },
+ {PORT_GET_UNIT_CONTENT, map[string]string{"sdk": "Python", "unitId": "unknown_unitId"},
+ ErrorResponse{
+ Code: "NOT_FOUND",
+ Message: "unit not found",
+ },
+ },
+ } {
+ t.Log("Scenario", i)
+ port := os.Getenv(params.portEnvName)
+ if port == "" {
+ t.Fatal(params.portEnvName, "env not set")
+ }
+ url := "http://localhost:" + port
+
+ var resp ErrorResponse
+ err := Get(&resp, url, params.queryParams)
+ if err != nil {
+ t.Fatal(err)
+ }
+ assert.Equal(t, params.expected, resp)
+ }
+}
diff --git a/learning/tour-of-beam/backend/integration_tests/local.sh b/learning/tour-of-beam/backend/integration_tests/local.sh
new file mode 100644
index 00000000000..c19c3fb97c8
--- /dev/null
+++ b/learning/tour-of-beam/backend/integration_tests/local.sh
@@ -0,0 +1,55 @@
+#!/bin/bash -eux
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+export DATASTORE_PROJECT_ID=test-proj
+export DATASTORE_EMULATOR_HOST=localhost:8081
+export DATASTORE_EMULATOR_DATADIR=./datadir-$(date '+%H-%M-%S')
+export TOB_LEARNING_ROOT=./samples/learning-content
+
+export PORT_SDK_LIST=8801
+export PORT_GET_CONTENT_TREE=8802
+export PORT_GET_UNIT_CONTENT=8803
+
+mkdir "$DATASTORE_EMULATOR_DATADIR"
+
+docker-compose up -d
+
+go build -o tob_function cmd/main.go
+
+PORT=$PORT_SDK_LIST FUNCTION_TARGET=sdkList ./tob_function &
+PORT=$PORT_GET_CONTENT_TREE FUNCTION_TARGET=getContentTree ./tob_function &
+PORT=$PORT_GET_UNIT_CONTENT FUNCTION_TARGET=getUnitContent ./tob_function &
+
+sleep 5
+
+
+go run cmd/ci_cd/ci_cd.go
+
+
+go test -v --tags integration ./integration_tests/...
+
+pkill -P $$
+
+rm -f ./tob_function
+
+docker-compose down
+
+
+ls "$DATASTORE_EMULATOR_DATADIR"
+cat "$DATASTORE_EMULATOR_DATADIR/WEB-INF/index.yaml"
+
+diff -q "$DATASTORE_EMULATOR_DATADIR/WEB-INF/index.yaml" internal/storage/index.yaml || ( echo "index.yaml mismatch"; exit 1)
+
+
+rm -rf "$DATASTORE_EMULATOR_DATADIR"
diff --git a/learning/tour-of-beam/backend/internal/fs_content/load_test.go b/learning/tour-of-beam/backend/internal/fs_content/load_test.go
index def3e9c1cd6..b5c50424dc7 100644
--- a/learning/tour-of-beam/backend/internal/fs_content/load_test.go
+++ b/learning/tour-of-beam/backend/internal/fs_content/load_test.go
@@ -26,10 +26,10 @@ import (
func genUnitNode(id string) tob.Node {
return tob.Node{Type: tob.NODE_UNIT, Unit: &tob.Unit{
Id: id, Name: "Challenge Name",
- Description: "## Challenge description\n\nbla bla bla",
+ Description: "## Challenge description\n\nawesome description\n",
Hints: []string{
- "## Hint 1\n\napply yourself :)",
- "## Hint 2\n\napply more",
+ "## Hint 1\n\nhint 1",
+ "## Hint 2\n\nhint 2",
},
}}
}
diff --git a/learning/tour-of-beam/backend/internal/storage/image/Dockerfile b/learning/tour-of-beam/backend/internal/storage/image/Dockerfile
index 071e7e37c92..0ff411bf352 100644
--- a/learning/tour-of-beam/backend/internal/storage/image/Dockerfile
+++ b/learning/tour-of-beam/backend/internal/storage/image/Dockerfile
@@ -23,8 +23,6 @@ FROM google/cloud-sdk:$GCLOUD_SDK_VERSION
# Volume to persist Datastore data
VOLUME /opt/data
-# RUN mkdir -p /opt/data/WEB-INF
-COPY index.yaml /opt/data/WEB-INF/index.yaml
COPY start-datastore.sh .
EXPOSE 8081
diff --git a/learning/tour-of-beam/backend/internal/storage/image/index.yaml b/learning/tour-of-beam/backend/internal/storage/image/index.yaml
deleted file mode 100644
index c59bc238758..00000000000
--- a/learning/tour-of-beam/backend/internal/storage/image/index.yaml
+++ /dev/null
@@ -1,29 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-indexes:
-- kind: tb_learning_module
- ancestor: yes
- properties:
- - name: order
-
-- kind: tb_learning_node
- ancestor: yes
- properties:
- - name: level
- - name: order
- - name: id
- - name: name
- - name: type
\ No newline at end of file
diff --git a/learning/tour-of-beam/backend/internal/storage/index.yaml b/learning/tour-of-beam/backend/internal/storage/index.yaml
new file mode 100644
index 00000000000..fa78d72f948
--- /dev/null
+++ b/learning/tour-of-beam/backend/internal/storage/index.yaml
@@ -0,0 +1,20 @@
+indexes:
+# AUTOGENERATED
+
+# This index.yaml is automatically updated whenever the Cloud Datastore
+# emulator detects that a new type of query is run. If you want to manage the
+# index.yaml file manually, remove the "# AUTOGENERATED" marker line above.
+# If you want to manage some indexes manually, move them above the marker line.
+
+- kind: "tb_learning_module"
+ ancestor: yes
+ properties:
+ - name: "order"
+- kind: "tb_learning_node"
+ ancestor: yes
+ properties:
+ - name: "level"
+ - name: "order"
+ - name: "id"
+ - name: "name"
+ - name: "type"
diff --git a/learning/tour-of-beam/backend/samples/api/get_unit_content.json b/learning/tour-of-beam/backend/samples/api/get_unit_content.json
index 0774f09116f..82337277ff0 100644
--- a/learning/tour-of-beam/backend/samples/api/get_unit_content.json
+++ b/learning/tour-of-beam/backend/samples/api/get_unit_content.json
@@ -1,15 +1,9 @@
{
- "unitId": "1.1",
- "name": "Basic concepts",
- "description": "Lorem ipsum...",
+ "unitId": "challenge1",
+ "name": "Challenge Name",
+ "description": "## Challenge description\n\nawesome description\n",
"hints" : [
- "## Hint 1\n\napply yourself :)",
- "## Hint 2\n\napply more"
- ],
-
- "taskSnippetId": "taskSnippetId",
- "solutionSnippetId": "solutionSnippetId",
-
- "userSnippetId": "userSnippetId",
- "isCompleted": true
+ "## Hint 1\n\nhint 1",
+ "## Hint 2\n\nhint 2"
+ ]
}
diff --git a/learning/tour-of-beam/backend/samples/api/get_unit_content_full.json b/learning/tour-of-beam/backend/samples/api/get_unit_content_full.json
new file mode 100644
index 00000000000..3fc1bc26e53
--- /dev/null
+++ b/learning/tour-of-beam/backend/samples/api/get_unit_content_full.json
@@ -0,0 +1,15 @@
+{
+ "unitId": "challenge1",
+ "name": "Challenge Name",
+ "description": "## Challenge description\n\nawesome description\n",
+ "hints" : [
+ "## Hint 1\n\nhint 1",
+ "## Hint 2\n\nhint 2"
+ ],
+
+ "taskSnippetId": "taskSnippetId",
+ "solutionSnippetId": "solutionSnippetId",
+
+ "userSnippetId": "userSnippetId",
+ "isCompleted": true
+}
diff --git a/learning/tour-of-beam/backend/samples/learning-content/java/module 1/unit-challenge/description.md b/learning/tour-of-beam/backend/samples/learning-content/java/module 1/unit-challenge/description.md
index 1906064ed20..ee159851f94 100644
--- a/learning/tour-of-beam/backend/samples/learning-content/java/module 1/unit-challenge/description.md
+++ b/learning/tour-of-beam/backend/samples/learning-content/java/module 1/unit-challenge/description.md
@@ -1,3 +1,3 @@
## Challenge description
-bla bla bla
\ No newline at end of file
+awesome description
diff --git a/learning/tour-of-beam/backend/samples/learning-content/java/module 1/unit-challenge/hint1.md b/learning/tour-of-beam/backend/samples/learning-content/java/module 1/unit-challenge/hint1.md
index e5d1a67fa90..0d023e307f5 100644
--- a/learning/tour-of-beam/backend/samples/learning-content/java/module 1/unit-challenge/hint1.md
+++ b/learning/tour-of-beam/backend/samples/learning-content/java/module 1/unit-challenge/hint1.md
@@ -1,3 +1,3 @@
## Hint 1
-apply yourself :)
\ No newline at end of file
+hint 1
\ No newline at end of file
diff --git a/learning/tour-of-beam/backend/samples/learning-content/java/module 1/unit-challenge/hint2.md b/learning/tour-of-beam/backend/samples/learning-content/java/module 1/unit-challenge/hint2.md
index 0c425751be9..92b0bf0af2f 100644
--- a/learning/tour-of-beam/backend/samples/learning-content/java/module 1/unit-challenge/hint2.md
+++ b/learning/tour-of-beam/backend/samples/learning-content/java/module 1/unit-challenge/hint2.md
@@ -1,3 +1,3 @@
## Hint 2
-apply more
\ No newline at end of file
+hint 2
\ No newline at end of file
diff --git a/learning/tour-of-beam/backend/samples/learning-content/java/module 2/unit-challenge/description.md b/learning/tour-of-beam/backend/samples/learning-content/java/module 2/unit-challenge/description.md
index 1906064ed20..ee159851f94 100644
--- a/learning/tour-of-beam/backend/samples/learning-content/java/module 2/unit-challenge/description.md
+++ b/learning/tour-of-beam/backend/samples/learning-content/java/module 2/unit-challenge/description.md
@@ -1,3 +1,3 @@
## Challenge description
-bla bla bla
\ No newline at end of file
+awesome description
diff --git a/learning/tour-of-beam/backend/samples/learning-content/java/module 2/unit-challenge/hint1.md b/learning/tour-of-beam/backend/samples/learning-content/java/module 2/unit-challenge/hint1.md
index e5d1a67fa90..0d023e307f5 100644
--- a/learning/tour-of-beam/backend/samples/learning-content/java/module 2/unit-challenge/hint1.md
+++ b/learning/tour-of-beam/backend/samples/learning-content/java/module 2/unit-challenge/hint1.md
@@ -1,3 +1,3 @@
## Hint 1
-apply yourself :)
\ No newline at end of file
+hint 1
\ No newline at end of file
diff --git a/learning/tour-of-beam/backend/samples/learning-content/java/module 2/unit-challenge/hint2.md b/learning/tour-of-beam/backend/samples/learning-content/java/module 2/unit-challenge/hint2.md
index 0c425751be9..92b0bf0af2f 100644
--- a/learning/tour-of-beam/backend/samples/learning-content/java/module 2/unit-challenge/hint2.md
+++ b/learning/tour-of-beam/backend/samples/learning-content/java/module 2/unit-challenge/hint2.md
@@ -1,3 +1,3 @@
## Hint 2
-apply more
\ No newline at end of file
+hint 2
\ No newline at end of file
diff --git a/learning/tour-of-beam/backend/samples/learning-content/python/module 1/group/unit-challenge/description.md b/learning/tour-of-beam/backend/samples/learning-content/python/module 1/group/unit-challenge/description.md
index 1906064ed20..ee159851f94 100644
--- a/learning/tour-of-beam/backend/samples/learning-content/python/module 1/group/unit-challenge/description.md
+++ b/learning/tour-of-beam/backend/samples/learning-content/python/module 1/group/unit-challenge/description.md
@@ -1,3 +1,3 @@
## Challenge description
-bla bla bla
\ No newline at end of file
+awesome description
diff --git a/learning/tour-of-beam/backend/samples/learning-content/python/module 1/group/unit-challenge/hint1.md b/learning/tour-of-beam/backend/samples/learning-content/python/module 1/group/unit-challenge/hint1.md
index e5d1a67fa90..0d023e307f5 100644
--- a/learning/tour-of-beam/backend/samples/learning-content/python/module 1/group/unit-challenge/hint1.md
+++ b/learning/tour-of-beam/backend/samples/learning-content/python/module 1/group/unit-challenge/hint1.md
@@ -1,3 +1,3 @@
## Hint 1
-apply yourself :)
\ No newline at end of file
+hint 1
\ No newline at end of file
diff --git a/learning/tour-of-beam/backend/samples/learning-content/python/module 1/group/unit-challenge/hint2.md b/learning/tour-of-beam/backend/samples/learning-content/python/module 1/group/unit-challenge/hint2.md
index 0c425751be9..92b0bf0af2f 100644
--- a/learning/tour-of-beam/backend/samples/learning-content/python/module 1/group/unit-challenge/hint2.md
+++ b/learning/tour-of-beam/backend/samples/learning-content/python/module 1/group/unit-challenge/hint2.md
@@ -1,3 +1,3 @@
## Hint 2
-apply more
\ No newline at end of file
+hint 2
\ No newline at end of file