<!--
    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.
-->

# Apache Beam Playground

## About

Beam Playground is an interactive environment to try out Beam transforms and examples.
The vision for the Playground is to be a web application where users can try out Beam
without having to install/initialize a Beam environment.

## Getting Started

### Run

See [playground/README.md](../README.md) for details on requirements and setup.

The following command is used to build and serve the frontend app locally:

```bash
flutter run -d chrome
```

### Build

Run the following command to generate a release build:

```bash
flutter build web
```

This produces `build/web` directory with static files. Deploy them to your web server.

### Backend Lookup

The file [playground_components/lib/src/constants/backend_urls.dart](playground_components/lib/src/constants/backend_urls.dart)
is the location for backend-related constants.

If the `backendUrlOverrides` map contains a URL for a server then only it will be attempted
for the given container. This is useful for running backend locally.

Otherwise following patterns are tried when looking up the backend servers:
1. Prepending the frontend host with `router.`, `go.`, `java.`, `python.`, `scio.`.
2. Prepending the the default production frontend URL with the same.

### Docker

The app is deployed to production as a Docker container.
You can also run it in Docker locally. This is useful if:

1. You do not have Flutter and do not want to install it.
2. You want to mimic the release environment in the closest way possible.

To run the frontend app with Docker, run this in the frontend directory:

```bash
docker build -t playground-frontend .
docker run -p 1234:8080 playground-frontend
```

The container sets up NGINX on port 8080.
This example exposes it as port 1234 on the host,
and the app can be accessed at http://localhost:1234

## Code Generation

This project relies on generated code for some functionality:
deserializers, test mocks, constants for asset files,
extracted Beam symbols for the editor, etc.

All generated files are version-controlled, so after checkout the project is immediately runnable.
However, after changes you may need to re-run code generation.

### Standard Dart Code Generator

Most of the generated files are produced by running the standard Dart code generator.
This only requires Flutter, but must be called on multiple locations.
For convenience, run this single command:

```bash
./gradlew :playground:frontend:generateCode
```

### Generating Beam Symbols Dictionaries

Requirements:

- Python 3.8+ with packages: `ast`, `pyyaml`.

Other SDKs will add more requirements as we add extraction scripts for them.

To generate all project's generated files including symbol dictionaries, run:

```bash
./gradlew :playground:frontend:generate
```

### Deleting Generated Files

For consistency, it is recommended that you delete and re-generate all files before committing
if you have all required tools on your machine. To delete all generated files, run:

```bash
./gradlew :playground:frontend:cleanGenerated
```

## Validation

### Pre-commit Checks

To run all pre-commit checks, execute this in the beam root:

```bash
./gradlew :playground:frontend:precommit
```

This includes:

- Tests.
- Linter.

### Code style

Code can be automatically reformatted using:

```bash
flutter format ./lib
```

### Unit Tests

To delete all generated files and re-generate them again and then run tests:

```bash
./gradlew :playground:frontend:playground_components:test
./gradlew :playground:frontend:test
```

To run tests without re-generating files:

```bash
cd playground/frontend/playground_components
flutter test
cd ..
flutter test
```

### Integration Tests

Integration tests currently can be run only on a local development machine.
Server testing has not been verified yet.

1. Install Google Chrome: https://www.google.com/chrome/
2. Install Chrome Driver: https://chromedriver.chromium.org/downloads
   - Note: This GitHub action installs both Chrome and Chrome Driver:
     https://github.com/nanasess/setup-chromedriver/blob/a249caaaad10fd12103028fd509853c2229eb6e6/lib/setup-chromedriver.sh
3. Run it on port 4444: `chromedriver --port=4444`
4. Run:

```bash
# To run in a visible Chrome window:
./gradlew :playground:frontend:integrationTest

# Headless run without a browser window:
./gradlew :playground:frontend:integrationTest -PdeviceId=web-server
```

## Localization

The project is in the process of migrating from
[the built-in Flutter localization](https://docs.flutter.dev/development/accessibility-and-localization/internationalization)
to [easy_localization](https://pub.dev/packages/easy_localization).
It temporarily uses both ways.

### Flutter Built-in Localization

To add a new localization, follow next steps:

1. Create app_YOUR_LOCALE_CODE.arb file with your key-translation pairs, except description tags, in lib/l10n directory (use app_en.arb as example).

2. Add Locale('YOUR_LOCALE_CODE') object to static const locales variable in lib/l10n/l10n.dart file.

3. Run the following command to generate a build and localization files:

```bash
flutter build web
```

### easy_localization

To add a new localization (using `fr` as an example):

1. Create `playground_components/assets/translations/fr.yaml`.
   Fill it with content copied from an existing translation file in another language.

2. Create `assets/translations/fr.yaml`.
   Fill it with content copied from an existing translation file in another language.

3. Add the locale to the list in `lib/l10n/l10n.dart`.

## URLs and Embedding

### Linking to a single example

#### 1. Linking to a catalog example by path

`https://play.beam.apache.org/?path=SDK_JAVA_AggregationMax&sdk=java`

Handled by `StandardExampleLoader`.

#### 2. Linking to the default catalog example

`https://play.beam.apache.org/?sdk=python&default=true`

Handled by `CatalogDefaultExampleLoader`.

#### 3. Linking to a user-shared example

`https://play.beam.apache.org/?sdk=java&shared=sdFdNV324HC`

Handled by `UserSharedExampleLoader`.

#### 4. Linking to an example by URL

`https://play.beam.apache.org/?sdk=go&url=https://raw.githubusercontent.com/golang/go/master/src/fmt/format.go`

Handled by `HttpExampleLoader`. The server with the example file must allow
the cross-origin access. GitHub is known to allow it.
Some servers may have different cross-origin policies when requested from localhost
and other domains.

#### 5. Linking to an empty editor

`https://play.beam.apache.org/?sdk=go&empty=true`

Handled by `EmptyExampleLoader`.

### Passing view options

Additional options may be passed with any of the above URL patterns.
For them to work, the example must contain sections with the following syntax:

```
// [START section_name]
void method() {
...
}
// [END section_name]
```

See more on the syntax and limitations in the
[README of the editor](https://pub.dev/packages/flutter_code_editor)
that Playground uses.

These options can be combined.

#### Read-only sections

Add `readonly` parameter with comma-separated section names:

`https://play.beam.apache.org/?sdk=go&url=https://raw.githubusercontent.com/GoogleCloudPlatform/golang-samples/main/iam/snippets/roles_get.go&readonly=iam_get_role`

#### Folding everything except sections

Add `unfold` parameter with comma-separated section names:

`https://play.beam.apache.org/?sdk=go&url=https://raw.githubusercontent.com/GoogleCloudPlatform/golang-samples/main/iam/snippets/roles_get.go&unfold=iam_get_role`

This folds all foldable blocks that do not overlap with
any of the given sections.

#### Hiding everything except a section

Add `show` parameter with a single section name:

`https://play.beam.apache.org/?sdk=go&url=https://raw.githubusercontent.com/GoogleCloudPlatform/golang-samples/main/iam/snippets/roles_get.go&show=iam_get_role`

It is still the whole snippet that is sent for execution although only the given section
is visible.

This also makes the editor read-only so the user cannot add code that conflicts
with the hidden text.

### Linking to multiple examples

With the above URLs, when the SDK is switched the following will be shown:

- The catalog default example for the new SDK in the standalone playground.
- The empty editor for the new SDK in the embedded playground.

This can be changed by linking to multiple examples, up to one per SDK.

For this purpose, make a JSON array with any combination of parameters that
are allowed for loading single examples, for instance:

```json
[
   {
      "sdk": "java",
      "path": "SDK_JAVA_AggregationMax"
   },
   {
      "sdk": "go",
      "url": "https://raw.githubusercontent.com/GoogleCloudPlatform/golang-samples/main/iam/snippets/roles_get.go",
      "readonly": "iam_get_role"
   }
]
```

Then pass it in`examples` query parameter like this:

`https://play.beam.apache.org/?sdk=go&examples=[{"sdk":"java","path":"SDK_JAVA_AggregationMax"},{"sdk":"go","url":"https://raw.githubusercontent.com/GoogleCloudPlatform/golang-samples/main/iam/snippets/roles_get.go","readonly":"iam_get_role"}]`

This starts with the Go example loaded from the URL.
If SDK is then switched to Java, the `AggregationMax` catalog example is loaded for it.
If SDK is switched to any other one, the default example for that SDK is loaded
because no override was provided.

### Embedded vs Standalone Playground URLs

Embedded Playground is a simplified interface supporting all the same features
as the primary interface which is also known as the Standalone Playground.

The embedded Playground URLs start with `https://play.beam.apache.org/embedded`
and use the same query string parameters as the standalone playground.

Additionally the Embedded playground supports the following parameters:

- `editable=0` to make the editor read-only.

### Embedding into HTML

Use the `<iframe>` tag to embed playground with any of the above URL patterns, for example:

```html
<iframe
  src="https://play-dev.beam.apache.org/embedded?path=SDK_JAVA_AggregationMax&sdk=java"
  width="800px"
  height="500px"
  allow="clipboard-write"
/>
```

Note that for the simplified embedded interface you need to use `/embedded` in the URL
otherwise the more complex default interface is shown.

Such URLs and `iframe` code can also be generated by clicking "Share my code" button
in the Standalone Playground.

### Embedding into the Beam documentation

Beam documentation is written in Markdown and uses [Hugo Markdown preprocessor](https://gohugo.io).
Use the custom shortcodes to embed Playground into the documentation:

- `playground` shortcode, see [a comment in it](https://github.com/apache/beam/blob/master/website/www/site/layouts/shortcodes/playground.html) for a complete example.
- `playground_snippet` shortcode, see [a comment in it](https://github.com/apache/beam/blob/master/website/www/site/layouts/shortcodes/playground_snippet.html) for all supported options.

These shortcodes generate an `iframe` with the URLs described above.

## Contribution guide

If you'd like to contribute to the Apache Beam Playground website, read
our [contribution guide](CONTRIBUTE.md) where you can find detailed instructions on how to work with
the playground.
