├── .dockerignore ├── .drone.yml ├── .env ├── .gitattributes ├── .github ├── .mlc_config.json ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── question.md ├── PULL_REQUEST_TEMPLATE.md ├── component_owners.yml ├── dependabot.yml └── workflows │ ├── assign-reviewers.yml │ ├── build-images.yml │ ├── checks.yml │ ├── gradle-wrapper-validation.yml │ ├── label-pr.yml │ ├── nightly-release.yml │ ├── release.yml │ ├── run-integration-tests.yml │ └── stale.yml ├── .gitignore ├── .licenserc.json ├── .markdownlint.yaml ├── .yamlignore ├── .yamllint ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── docker-compose.minimal.yml ├── docker-compose.nocache.yml ├── docker-compose.yml ├── ide-gen-proto.sh ├── internal └── tools │ ├── go.mod │ ├── go.sum │ ├── sanitycheck.py │ └── tools.go ├── kubernetes └── opentelemetry-demo.yaml ├── package-lock.json ├── package.json ├── pb └── demo.proto ├── renovate.json5 ├── src ├── accountingservice │ ├── Dockerfile │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── kafka │ │ ├── consumer.go │ │ └── trace_interceptor.go │ ├── main.go │ └── tools.go ├── adservice │ ├── .java-version │ ├── Dockerfile │ ├── README.md │ ├── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle │ └── src │ │ └── main │ │ ├── java │ │ └── oteldemo │ │ │ ├── AdService.java │ │ │ └── problempattern │ │ │ ├── CPULoad.java │ │ │ ├── GarbageCollectionTrigger.java │ │ │ └── MemoryUtils.java │ │ └── resources │ │ └── log4j2.xml ├── cartservice │ ├── NuGet.config │ ├── README.md │ ├── cartservice.sln │ ├── src │ │ ├── .dockerignore │ │ ├── Dockerfile │ │ ├── Program.cs │ │ ├── appsettings.json │ │ ├── cartservice.csproj │ │ ├── cartstore │ │ │ ├── ICartStore.cs │ │ │ └── RedisCartStore.cs │ │ └── services │ │ │ └── CartService.cs │ └── tests │ │ ├── CartServiceTests.cs │ │ └── cartservice.tests.csproj ├── checkoutservice │ ├── Dockerfile │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── kafka │ │ └── producer.go │ ├── main.go │ ├── money │ │ ├── money.go │ │ └── money_test.go │ └── tools.go ├── currencyservice │ ├── .dockerignore │ ├── CMakeLists.txt │ ├── Dockerfile │ ├── README.md │ ├── data │ │ └── currency_conversion.json │ ├── proto │ │ └── grpc │ │ │ └── health │ │ │ └── v1 │ │ │ └── health.proto │ └── src │ │ ├── logger_common.h │ │ ├── meter_common.h │ │ ├── server.cpp │ │ └── tracer_common.h ├── emailservice │ ├── .dockerignore │ ├── .ruby-version │ ├── Dockerfile │ ├── Gemfile │ ├── Gemfile.lock │ ├── README.md │ ├── email_server.rb │ └── views │ │ └── confirmation.erb ├── flagd │ └── demo.flagd.json ├── frauddetectionservice │ ├── .dockerignore │ ├── Dockerfile │ ├── README.md │ ├── build.gradle.kts │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── src │ │ └── main │ │ ├── kotlin │ │ └── frauddetectionservice │ │ │ └── main.kt │ │ └── resources │ │ └── log4j2.xml ├── frontend │ ├── .dockerignore │ ├── .eslintrc │ ├── .prettierignore │ ├── .prettierrc │ ├── Dockerfile │ ├── Dockerfile.ci │ ├── Dockerfile.cypress │ ├── README.md │ ├── components │ │ ├── Ad │ │ │ ├── Ad.styled.ts │ │ │ ├── Ad.tsx │ │ │ └── index.ts │ │ ├── Banner │ │ │ ├── Banner.styled.ts │ │ │ ├── Banner.tsx │ │ │ └── index.ts │ │ ├── Button │ │ │ ├── Button.tsx │ │ │ └── index.ts │ │ ├── Cart │ │ │ ├── CartDetail.tsx │ │ │ └── EmptyCart.tsx │ │ ├── CartDropdown │ │ │ ├── CartDropdown.styled.ts │ │ │ ├── CartDropdown.tsx │ │ │ └── index.ts │ │ ├── CartIcon │ │ │ ├── CartIcon.styled.ts │ │ │ ├── CartIcon.tsx │ │ │ └── index.ts │ │ ├── CartItems │ │ │ ├── CartItem.tsx │ │ │ ├── CartItems.styled.ts │ │ │ ├── CartItems.tsx │ │ │ └── index.ts │ │ ├── CheckoutForm │ │ │ ├── CheckoutForm.styled.ts │ │ │ ├── CheckoutForm.tsx │ │ │ └── index.ts │ │ ├── CheckoutItem │ │ │ ├── CheckoutItem.styled.ts │ │ │ ├── CheckoutItem.tsx │ │ │ └── index.ts │ │ ├── CurrencySwitcher │ │ │ ├── CurrencySwitcher.styled.ts │ │ │ ├── CurrencySwitcher.tsx │ │ │ └── index.ts │ │ ├── Footer │ │ │ ├── Footer.styled.ts │ │ │ ├── Footer.tsx │ │ │ └── index.ts │ │ ├── Header │ │ │ ├── Header.styled.ts │ │ │ ├── Header.tsx │ │ │ └── index.ts │ │ ├── Input │ │ │ ├── Input.styled.ts │ │ │ ├── Input.tsx │ │ │ └── index.ts │ │ ├── Layout │ │ │ ├── Layout.tsx │ │ │ └── index.ts │ │ ├── PlatformFlag │ │ │ ├── PlatformFlag.styled.ts │ │ │ ├── PlatformFlag.tsx │ │ │ └── index.ts │ │ ├── ProductCard │ │ │ ├── ProductCard.styled.ts │ │ │ ├── ProductCard.tsx │ │ │ └── index.ts │ │ ├── ProductList │ │ │ ├── ProductList.styled.ts │ │ │ ├── ProductList.tsx │ │ │ └── index.ts │ │ ├── ProductPrice │ │ │ ├── ProductPrice.tsx │ │ │ └── index.ts │ │ ├── Recommendations │ │ │ ├── Recommendations.styled.ts │ │ │ ├── Recommendations.tsx │ │ │ └── index.ts │ │ └── Select │ │ │ ├── Select.styled.ts │ │ │ ├── Select.tsx │ │ │ └── index.ts │ ├── cypress.config.ts │ ├── cypress │ │ └── e2e │ │ │ ├── Checkout.cy.ts │ │ │ ├── Home.cy.ts │ │ │ └── ProductDetail.cy.ts │ ├── gateways │ │ ├── Api.gateway.ts │ │ ├── Session.gateway.ts │ │ └── rpc │ │ │ ├── Ad.gateway.ts │ │ │ ├── Cart.gateway.ts │ │ │ ├── Checkout.gateway.ts │ │ │ ├── Currency.gateway.ts │ │ │ ├── ProductCatalog.gateway.ts │ │ │ ├── Recommendations.gateway.ts │ │ │ └── Shipping.gateway.ts │ ├── middleware.ts │ ├── next.config.js │ ├── package-lock.json │ ├── package.json │ ├── pages │ │ ├── _app.tsx │ │ ├── _document.tsx │ │ ├── api │ │ │ ├── cart.ts │ │ │ ├── checkout.ts │ │ │ ├── currency.ts │ │ │ ├── data.ts │ │ │ ├── products │ │ │ │ ├── [productId] │ │ │ │ │ └── index.ts │ │ │ │ └── index.ts │ │ │ ├── recommendations.ts │ │ │ └── shipping.ts │ │ ├── cart │ │ │ ├── checkout │ │ │ │ └── [orderId] │ │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── index.tsx │ │ └── product │ │ │ └── [productId] │ │ │ └── index.tsx │ ├── pb │ │ └── demo.proto │ ├── providers │ │ ├── Ad.provider.tsx │ │ ├── Cart.provider.tsx │ │ └── Currency.provider.tsx │ ├── public │ │ ├── favicon.ico │ │ └── icons │ │ │ ├── Cart.svg │ │ │ ├── CartIcon.svg │ │ │ ├── Check.svg │ │ │ └── Chevron.svg │ ├── services │ │ └── ProductCatalog.service.ts │ ├── styles │ │ ├── Cart.styled.ts │ │ ├── Checkout.styled.ts │ │ ├── Home.styled.ts │ │ ├── ProductDetail.styled.ts │ │ ├── Theme.ts │ │ ├── globals.css │ │ └── style.d.ts │ ├── tsconfig.json │ ├── types │ │ └── Cart.ts │ └── utils │ │ ├── Cypress.ts │ │ ├── Request.ts │ │ ├── enums │ │ └── AttributeNames.ts │ │ └── telemetry │ │ ├── FaroTracer.ts │ │ ├── FrontendTracer.ts │ │ ├── Instrumentation.js │ │ ├── InstrumentationMiddleware.ts │ │ └── SessionIdProcessor.ts ├── frontendproxy │ ├── Dockerfile │ ├── README.md │ └── envoy.tmpl.yaml ├── grafana │ ├── grafana.ini │ └── provisioning │ │ ├── dashboards │ │ ├── demo.yaml │ │ └── demo │ │ │ ├── demo-dashboard.json │ │ │ ├── opentelemetry-collector-data-flow.json │ │ │ ├── opentelemetry-collector.json │ │ │ └── spanmetrics-dashboard.json │ │ └── datasources │ │ ├── default.yaml │ │ ├── jaeger.yaml │ │ └── opensearch.yaml ├── imageprovider │ ├── Dockerfile │ ├── nginx.conf.template │ └── static │ │ ├── Banner.png │ │ ├── opentelemetry-demo-logo.png │ │ └── products │ │ ├── EclipsmartTravelRefractorTelescope.jpg │ │ ├── LensCleaningKit.jpg │ │ ├── NationalParkFoundationExplorascope.jpg │ │ ├── OpticalTubeAssembly.jpg │ │ ├── RedFlashlight.jpg │ │ ├── RoofBinoculars.jpg │ │ ├── SolarFilter.jpg │ │ ├── SolarSystemColorImager.jpg │ │ ├── StarsenseExplorer.jpg │ │ └── TheCometBook.jpg ├── kafka │ ├── Dockerfile │ └── README.md ├── loadgenerator │ ├── Dockerfile │ ├── README.md │ ├── locustfile.py │ ├── people.json │ └── requirements.txt ├── otelcollector │ ├── otelcol-config-extras.yml │ └── otelcol-config.yml ├── paymentservice │ ├── .dockerignore │ ├── Dockerfile │ ├── README.md │ ├── charge.js │ ├── index.js │ ├── logger.js │ ├── opentelemetry.js │ ├── package-lock.json │ └── package.json ├── productcatalogservice │ ├── Dockerfile │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── main.go │ ├── products │ │ └── products.json │ └── tools.go ├── prometheus │ └── prometheus-config.yaml ├── quoteservice │ ├── .dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── README.md │ ├── app │ │ ├── dependencies.php │ │ ├── routes.php │ │ └── settings.php │ ├── composer.json │ ├── public │ │ └── index.php │ └── src │ │ └── Application │ │ └── Settings │ │ ├── Settings.php │ │ └── SettingsInterface.php ├── recommendationservice │ ├── Dockerfile │ ├── README.md │ ├── logger.py │ ├── metrics.py │ ├── recommendation_server.py │ └── requirements.txt └── shippingservice │ ├── .dockerignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── Dockerfile │ ├── README.md │ ├── build.rs │ └── src │ ├── main.rs │ ├── shipping_service.rs │ ├── shipping_service │ ├── quote.rs │ └── tracking.rs │ ├── telemetry.rs │ └── telemetry │ ├── logs_conf.rs │ ├── resources_conf.rs │ └── traces_conf.rs └── test ├── README.md └── tracetesting ├── Dockerfile ├── ad-service ├── all.yaml └── get.yaml ├── cart-service ├── add-item-to-cart.yaml ├── all.yaml ├── check-if-cart-is-empty.yaml ├── check-if-cart-is-populated.yaml └── empty-cart.yaml ├── checkout-service ├── all.yaml └── place-order.yaml ├── cli-config.yml ├── currency-service ├── all.yaml ├── convert.yaml └── supported.yaml ├── email-service ├── all.yaml └── confirmation.yaml ├── frontend-service ├── 01-see-ads.yaml ├── 02-get-product-recommendation.yaml ├── 03-browse-product.yaml ├── 04-add-product-to-cart.yaml ├── 05-view-cart.yaml ├── 06-checking-out-cart.yaml └── all.yaml ├── payment-service ├── all.yaml ├── amex-credit-card-not-allowed.yaml ├── expired-credit-card.yaml ├── invalid-credit-card.yaml └── valid-credit-card.yaml ├── product-catalog-service ├── all.yaml ├── get.yaml ├── list.yaml └── search.yaml ├── recommendation-service ├── all.yaml └── list.yaml ├── run.bash ├── shipping-service ├── all.yaml ├── empty-quote.yaml ├── order.yaml └── quote.yaml ├── tracetest-config.yaml └── tracetest-provision.yaml /.dockerignore: -------------------------------------------------------------------------------- 1 | 2 | 3 | .github 4 | docs 5 | internal 6 | src/*/README.md 7 | src/*/Dockerfile 8 | src/*/*/Dockerfile 9 | 10 | ################################### 11 | # currencyservice 12 | ./src/currencyservice/client.js 13 | ./src/currencyservice/node_modules/ 14 | ################################### 15 | 16 | ################################### 17 | # featureflagservice 18 | # Common development/test artifacts 19 | ./src/featureflagservice/cover/ 20 | ./src/featureflagservice/doc/ 21 | ./src/featureflagservice/test/ 22 | ./src/featureflagservice/tmp/ 23 | ./src/featureflagservice/.elixir_ls 24 | 25 | ################################### 26 | # frontend 27 | ./src/frontend/node_modules/ 28 | 29 | ################################### 30 | # shippingservice 31 | ./src/shippingservice/target 32 | ################################### 33 | 34 | # Mix artifacts 35 | ./src/featureflagservice/_build/ 36 | ./src/featureflagservice/deps/ 37 | ./src/featureflagservice/*.ez 38 | 39 | # Generated on crash by the VM 40 | ./src/featureflagservice/erl_crash.dump 41 | 42 | # Static artifacts - These should be fetched and built inside the Docker image 43 | ./src/featureflagservice/assets/node_modules/ 44 | ./src/featureflagservice/priv/static/assets/ 45 | ./src/featureflagservice/priv/static/cache_manifest.json 46 | ################################### 47 | 48 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | 2 | 3 | * text=auto 4 | 5 | gradlew text eol=lf 6 | -------------------------------------------------------------------------------- /.github/.mlc_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignorePatterns": [ 3 | {"pattern": "^http://localhost:8080"}, 4 | {"pattern": "^https://calendar.google.com/calendar"} 5 | ], 6 | "replacementPatterns": [ 7 | { 8 | "pattern": "^/", 9 | "replacement": "/github/workspace/" 10 | } 11 | ], 12 | "aliveStatusCodes": [ 13 | 200, 14 | 429 15 | ] 16 | } -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Code owners file. 2 | # This file controls who is tagged for review for any given pull request. 3 | 4 | # For anything not explicitly taken by someone else: 5 | * @open-telemetry/demo-approvers 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a report to help us improve 4 | labels: bug 5 | --- 6 | 7 | # Bug Report 8 | 9 | Which version of the demo you are using? (please provide either a specific 10 | [commit 11 | hash](https://github.com/open-telemetry/opentelemetry-demo/commits/main) 12 | or a specific 13 | [release](https://github.com/open-telemetry/opentelemetry-demo/releases)). 14 | 15 | ## Symptom 16 | 17 | A clear and concise description of what the bug is. 18 | 19 | **What is the expected behavior?** 20 | 21 | What do you expect to see? 22 | 23 | **What is the actual behavior?** 24 | 25 | What did you see instead? 26 | 27 | ## Reproduce 28 | 29 | Provide the minimum required steps to result in the issue you're observing. 30 | 31 | We will close this issue if: 32 | 33 | * The steps you provided are complex. 34 | * If we can not reproduce the behavior you're reporting. 35 | 36 | ## Additional Context 37 | 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | labels: enhancement 5 | --- 6 | 7 | # Feature Request 8 | 9 | Before opening a feature request against this repo, consider whether the feature 10 | should/could be implemented in the [other OpenTelemetry client 11 | libraries](https://github.com/open-telemetry/). If so, please [open an issue on 12 | opentelemetry-specification](https://github.com/open-telemetry/opentelemetry-specification/issues/new) 13 | first. 14 | 15 | **Is your feature request related to a problem?** 16 | 17 | If so, provide a concise description of the problem. 18 | 19 | **Describe the solution you'd like:** 20 | 21 | What do you want to happen instead? What is the expected behavior? 22 | 23 | **Describe alternatives you've considered.** 24 | 25 | Which alternative solutions or features have you considered? 26 | 27 | ## Additional Context 28 | 29 | Add any other context about the feature request here. 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Create a question to help us improve our knowledge base and documentation 4 | labels: question 5 | --- 6 | 7 | # Question 8 | 9 | Use [Github Discussions](https://github.com/open-telemetry/opentelemetry-demo/discussions/). 10 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | Please provide a brief description of the changes here. 4 | 5 | ## Merge Requirements 6 | 7 | For new features contributions please make sure you have completed the following 8 | essential items: 9 | 10 | * [ ] `CHANGELOG.md` updated to document new feature additions 11 | * [ ] Appropriate documentation updates in the [docs][] 12 | * [ ] Appropriate Helm chart updates in the [helm-charts][] 13 | 14 | 24 | 25 | Maintainers will not merge until the above have been completed. If you're unsure 26 | which docs need to be changed ping the 27 | [@open-telemetry/demo-approvers](https://github.com/orgs/open-telemetry/teams/demo-approvers). 28 | 29 | [docs]: https://opentelemetry.io/docs/demo/ 30 | [helm-charts]: https://github.com/open-telemetry/opentelemetry-helm-charts 31 | -------------------------------------------------------------------------------- /.github/component_owners.yml: -------------------------------------------------------------------------------- 1 | # Copyright The OpenTelemetry Authors 2 | # SPDX-License-Identifier: Apache-2.0 3 | # this file is used by .github/workflows/assign-reviewers.yml 4 | components: 5 | src/adservice: 6 | - jack-berg 7 | - mateuszrzeszutek 8 | - trask 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Copyright The OpenTelemetry Authors 2 | # SPDX-License-Identifier: Apache-2.0 3 | version: 2 4 | updates: 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | schedule: 8 | interval: "daily" 9 | labels: 10 | - "infra" 11 | -------------------------------------------------------------------------------- /.github/workflows/assign-reviewers.yml: -------------------------------------------------------------------------------- 1 | # Copyright The OpenTelemetry Authors 2 | # SPDX-License-Identifier: Apache-2.0 3 | # assigns reviewers to pull requests in a similar way as CODEOWNERS, but doesn't require reviewers 4 | # to have write access to the repository 5 | # see .github/component_owners.yaml for the list of components and their owners 6 | name: Assign reviewers 7 | 8 | on: 9 | # pull_request_target is needed instead of just pull_request 10 | # because repository write permission is needed to assign reviewers 11 | pull_request_target: 12 | 13 | jobs: 14 | assign-reviewers: 15 | if: github.repository == 'open-telemetry/opentelemetry-demo' 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: dyladan/component-owners@main 19 | with: 20 | # using this action to request review only (not assignment) 21 | assign-owners: false 22 | -------------------------------------------------------------------------------- /.github/workflows/gradle-wrapper-validation.yml: -------------------------------------------------------------------------------- 1 | # Copyright The OpenTelemetry Authors 2 | # SPDX-License-Identifier: Apache-2.0 3 | name: Gradle wrapper validation 4 | on: 5 | pull_request: 6 | paths: 7 | - '**/gradle/wrapper/**' 8 | push: 9 | paths: 10 | - '**/gradle/wrapper/**' 11 | 12 | jobs: 13 | validation: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - uses: gradle/wrapper-validation-action@v3.3.0 19 | -------------------------------------------------------------------------------- /.github/workflows/nightly-release.yml: -------------------------------------------------------------------------------- 1 | # Copyright The OpenTelemetry Authors 2 | # SPDX-License-Identifier: Apache-2.0 3 | name: Nightly Release 4 | 5 | on: 6 | schedule: 7 | # Runs at 00:00 UTC every day 8 | - cron: '0 0 * * *' 9 | 10 | jobs: 11 | build_and_push_images: 12 | uses: ./.github/workflows/build-images.yml 13 | if: github.repository == 'open-telemetry/opentelemetry-demo' 14 | with: 15 | push: true 16 | version: nightly-${{ github.run_id }} 17 | secrets: inherit 18 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # Copyright The OpenTelemetry Authors 2 | # SPDX-License-Identifier: Apache-2.0 3 | name: "Build and Publish" 4 | 5 | on: 6 | release: 7 | types: [published] 8 | 9 | jobs: 10 | build_and_push_images: 11 | uses: ./.github/workflows/build-images.yml 12 | if: github.repository == 'open-telemetry/opentelemetry-demo' 13 | with: 14 | push: true 15 | version: ${{ github.event.release.tag_name }} 16 | secrets: inherit 17 | -------------------------------------------------------------------------------- /.github/workflows/run-integration-tests.yml: -------------------------------------------------------------------------------- 1 | # Copyright The OpenTelemetry Authors 2 | # SPDX-License-Identifier: Apache-2.0 3 | name: Integration Tests 4 | 5 | on: 6 | pull_request_review: 7 | types: 8 | - submitted 9 | 10 | jobs: 11 | run_tests: 12 | runs-on: ubuntu-latest 13 | name: "Run CI" 14 | if: github.event.review.state == 'APPROVED' 15 | steps: 16 | - name: check out code 17 | uses: actions/checkout@v4 18 | - name: run tracetesting 19 | run: | 20 | make build && make run-tracetesting 21 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | # Copyright The OpenTelemetry Authors 2 | # SPDX-License-Identifier: Apache-2.0 3 | # Syntax: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions 4 | # Github Actions Stale: https://github.com/actions/stale 5 | 6 | name: "Close stale pull requests" 7 | on: 8 | schedule: 9 | - cron: "12 3 * * *" # arbitrary time not to DDOS GitHub 10 | 11 | jobs: 12 | stale: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/stale@v8 16 | with: 17 | stale-pr-message: 'This PR was marked stale due to lack of activity. It will be closed in 7 days.' 18 | close-pr-message: 'Closed as inactive. Feel free to reopen if this PR is still being worked on.' 19 | operations-per-run: 400 20 | days-before-pr-stale: 7 21 | days-before-issue-stale: -1 22 | days-before-pr-close: 7 23 | days-before-issue-close: -1 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | 3 | *~ 4 | *.iml 5 | *.ipr 6 | *.iws 7 | *.pyc 8 | .nb-gradle 9 | .nb-gradle-properties 10 | .swp 11 | .DS_Store 12 | \#*\# 13 | 14 | docker-compose.override.yml 15 | 16 | # Eclipse 17 | .classpath 18 | .project 19 | .settings 20 | 21 | bin/ 22 | obj/ 23 | .vs/ 24 | .vscode 25 | .gradle/ 26 | .idea/ 27 | build/ 28 | node_modules/ 29 | coverage 30 | .next/ 31 | out/ 32 | build 33 | next-env.d.ts 34 | vendor/ 35 | composer.lock 36 | .venv 37 | .dockerhub.env 38 | .ghcr.env 39 | 40 | src/frontend/cypress/videos 41 | src/frontend/cypress/screenshots 42 | src/shippingservice/target/ 43 | test/tracetesting/tracetesting-vars.yaml 44 | 45 | # Ignore copied/generated protobuf files 46 | /src/cartservice/src/protos/ 47 | /src/featureflagservice/proto/ 48 | /src/featureflagservice/src/ffs_demo_pb.erl 49 | /src/featureflagservice/src/ffs_service_*.erl 50 | /src/featureflagservice/src/oteldemo_*.erl 51 | /src/frauddetectionservice/src/main/proto 52 | /src/frontend/pb/ 53 | /src/frontend/protos/ 54 | /src/paymentservice/demo.proto 55 | /src/recommendationservice/demo_pb2*.py 56 | /src/shippingservice/proto/ 57 | /src/productcatalogservice/genproto 58 | /src/currencyservice/proto 59 | /src/checkoutservice/genproto 60 | /src/accountingservice/genproto 61 | 62 | .env.grafana 63 | .env.grafana.* 64 | -------------------------------------------------------------------------------- /.licenserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "**/*.{go,cs,h,cpp,js,ts,tsx,rs}": [ 3 | "// Copyright The OpenTelemetry Authors", 4 | "// SPDX-License-Identifier: Apache-2.0" 5 | ], 6 | "**/*.php": [ 7 | " [!NOTE] 24 | > [`protoc`](https://grpc.io/docs/protoc-installation/) is required. 25 | 26 | To regenerate gRPC code run: 27 | 28 | ```sh 29 | go generate 30 | ``` 31 | 32 | ## Bump dependencies 33 | 34 | To bump all dependencies run: 35 | 36 | ```sh 37 | go get -u -t ./... 38 | go mod tidy 39 | ``` 40 | -------------------------------------------------------------------------------- /src/accountingservice/tools.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build tools 5 | // +build tools 6 | 7 | package tools 8 | 9 | // This file follows the recommendation at 10 | // https://go.dev/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module 11 | // on how to pin tooling dependencies to a go.mod file. 12 | // This ensures that all systems use the same version of tools in addition to regular dependencies. 13 | 14 | import ( 15 | _ "google.golang.org/grpc/cmd/protoc-gen-go-grpc" 16 | _ "google.golang.org/protobuf/cmd/protoc-gen-go" 17 | ) 18 | -------------------------------------------------------------------------------- /src/adservice/.java-version: -------------------------------------------------------------------------------- 1 | 21.0 2 | -------------------------------------------------------------------------------- /src/adservice/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright The OpenTelemetry Authors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | 5 | FROM eclipse-temurin:21-jdk as builder 6 | 7 | WORKDIR /usr/src/app/ 8 | 9 | COPY ./src/adservice/gradlew* ./src/adservice/settings.gradle* ./src/adservice/build.gradle ./ 10 | COPY ./src/adservice/gradle ./gradle 11 | 12 | RUN ./gradlew 13 | RUN ./gradlew downloadRepos 14 | 15 | COPY ./src/adservice/ ./ 16 | COPY ./pb/ ./proto 17 | RUN ./gradlew installDist -PprotoSourceDir=./proto 18 | 19 | # ----------------------------------------------------------------------------- 20 | 21 | FROM eclipse-temurin:21-jre 22 | 23 | ARG version=2.3.0 24 | WORKDIR /usr/src/app/ 25 | 26 | COPY --from=builder /usr/src/app/ ./ 27 | # we ignore the version (which is from upstream) and use the latest version of the grafana distribution 28 | ADD --chmod=644 https://github.com/grafana/grafana-opentelemetry-java/releases/latest/download/grafana-opentelemetry-java.jar /usr/src/app/opentelemetry-javaagent.jar 29 | ENV JAVA_TOOL_OPTIONS=-javaagent:/usr/src/app/opentelemetry-javaagent.jar 30 | 31 | EXPOSE ${AD_SERVICE_PORT} 32 | ENTRYPOINT [ "./build/install/opentelemetry-demo-ad-service/bin/AdService" ] 33 | -------------------------------------------------------------------------------- /src/adservice/README.md: -------------------------------------------------------------------------------- 1 | # Ad Service 2 | 3 | The Ad service provides advertisement based on context keys. If no context keys 4 | are provided then it returns random ads. 5 | 6 | ## Building Locally 7 | 8 | The Ad service requires at least JDK 17 to build and uses gradlew to 9 | compile/install/distribute. Gradle wrapper is already part of the source code. 10 | To build Ad Service, run: 11 | 12 | ```sh 13 | ./gradlew installDist 14 | ``` 15 | 16 | It will create an executable script 17 | `src/adservice/build/install/oteldemo/bin/AdService`. 18 | 19 | To run the Ad Service: 20 | 21 | ```sh 22 | export AD_SERVICE_PORT=8080 23 | export FEATURE_FLAG_GRPC_SERVICE_ADDR=featureflagservice:50053 24 | ./build/install/opentelemetry-demo-ad-service/bin/AdService 25 | ``` 26 | 27 | ### Upgrading Gradle 28 | 29 | If you need to upgrade the version of gradle then run 30 | 31 | ```sh 32 | ./gradlew wrapper --gradle-version 33 | ``` 34 | 35 | ## Building Docker 36 | 37 | From the root of `opentelemetry-demo`, run: 38 | 39 | ```sh 40 | docker build --file ./src/adservice/Dockerfile ./ 41 | ``` 42 | -------------------------------------------------------------------------------- /src/adservice/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grafana/opentelemetry-demo/a635de2d949c1090075458ecbdb99fde24103693/src/adservice/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/adservice/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /src/adservice/settings.gradle: -------------------------------------------------------------------------------- 1 | 2 | rootProject.name = 'opentelemetry-demo-ad-service' 3 | -------------------------------------------------------------------------------- /src/adservice/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/cartservice/NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/cartservice/README.md: -------------------------------------------------------------------------------- 1 | # Cart Service 2 | 3 | This service stores user shopping carts in Redis. 4 | 5 | ## Local Build 6 | 7 | Run `dotnet restore` and `dotnet build`. 8 | 9 | Protobufs must be present in `./src/protos` 10 | 11 | ## Docker Build 12 | 13 | From the root directory, run: 14 | 15 | ```sh 16 | docker compose build cartservice 17 | ``` 18 | -------------------------------------------------------------------------------- /src/cartservice/src/.dockerignore: -------------------------------------------------------------------------------- 1 | **/*.sh 2 | **/*.bat 3 | **/bin/ 4 | **/obj/ 5 | **/out/ 6 | Dockerfile* -------------------------------------------------------------------------------- /src/cartservice/src/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright The OpenTelemetry Authors 2 | # SPDX-License-Identifier: Apache-2.0 3 | # Copyright 2021 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # https://mcr.microsoft.com/v2/dotnet/sdk/tags/list 18 | FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0.202 AS builder 19 | ARG TARGETARCH 20 | 21 | WORKDIR /usr/src/app/ 22 | 23 | COPY ./src/cartservice/ ./ 24 | COPY ./pb/ ./src/protos/ 25 | 26 | RUN dotnet restore ./src/cartservice.csproj -v d -r linux-musl-$TARGETARCH 27 | 28 | RUN dotnet publish ./src/cartservice.csproj -v d -r linux-musl-$TARGETARCH --no-restore -o /cartservice 29 | 30 | # ----------------------------------------------------------------------------- 31 | 32 | # https://mcr.microsoft.com/v2/dotnet/runtime-deps/tags/list 33 | FROM mcr.microsoft.com/dotnet/runtime-deps:8.0.3-alpine3.19 34 | 35 | WORKDIR /usr/src/app/ 36 | COPY --from=builder /cartservice/ ./ 37 | 38 | ENV DOTNET_HOSTBUILDER__RELOADCONFIGONCHANGE=false 39 | 40 | EXPOSE ${CART_SERVICE_PORT} 41 | ENTRYPOINT [ "./cartservice" ] 42 | -------------------------------------------------------------------------------- /src/cartservice/src/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*", 10 | "Kestrel": { 11 | "EndpointDefaults": { 12 | "Protocols": "Http2" 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/cartservice/src/cartstore/ICartStore.cs: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | using System.Threading.Tasks; 4 | 5 | namespace cartservice.cartstore; 6 | 7 | public interface ICartStore 8 | { 9 | void Initialize(); 10 | 11 | Task AddItemAsync(string userId, string productId, int quantity); 12 | Task EmptyCartAsync(string userId); 13 | 14 | Task GetCartAsync(string userId); 15 | 16 | bool Ping(); 17 | } 18 | -------------------------------------------------------------------------------- /src/cartservice/tests/cartservice.tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/checkoutservice/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright The OpenTelemetry Authors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | 5 | FROM golang:1.22-alpine AS builder 6 | 7 | WORKDIR /usr/src/app/ 8 | 9 | RUN apk update \ 10 | && apk add --no-cache make protobuf-dev 11 | 12 | RUN --mount=type=cache,target=/go/pkg/mod/ \ 13 | --mount=type=bind,source=./src/checkoutservice/go.sum,target=go.sum \ 14 | --mount=type=bind,source=./src/checkoutservice/go.mod,target=go.mod \ 15 | --mount=type=bind,source=./src/checkoutservice/tools.go,target=tools.go \ 16 | go mod download \ 17 | && go list -e -f '{{range .Imports}}{{.}} {{end}}' tools.go | CGO_ENABLED=0 xargs go install -mod=readonly 18 | 19 | RUN --mount=type=cache,target=/go/pkg/mod/ \ 20 | --mount=type=cache,target=/root/.cache/go-build \ 21 | --mount=type=bind,rw,source=./src/checkoutservice,target=. \ 22 | --mount=type=bind,rw,source=./pb,target=./pb \ 23 | protoc -I ./pb ./pb/demo.proto --go_out=./ --go-grpc_out=./ \ 24 | && go build -ldflags "-s -w" -o /go/bin/checkoutservice/ ./ 25 | 26 | FROM alpine 27 | 28 | WORKDIR /usr/src/app/ 29 | 30 | COPY --from=builder /go/bin/checkoutservice/ ./ 31 | 32 | EXPOSE ${CHECKOUT_SERVICE_PORT} 33 | ENTRYPOINT [ "./checkoutservice" ] 34 | -------------------------------------------------------------------------------- /src/checkoutservice/README.md: -------------------------------------------------------------------------------- 1 | # Checkout Service 2 | 3 | This service provides checkout services for the application. 4 | 5 | ## Local Build 6 | 7 | To build the service binary, run: 8 | 9 | ```sh 10 | go build -o /go/bin/checkoutservice/ 11 | ``` 12 | 13 | ## Docker Build 14 | 15 | From the root directory, run: 16 | 17 | ```sh 18 | docker compose build checkoutservice 19 | ``` 20 | 21 | ## Regenerate protos 22 | 23 | > [!NOTE] 24 | > [`protoc`](https://grpc.io/docs/protoc-installation/) is required. 25 | 26 | To regenerate gRPC code run: 27 | 28 | ```sh 29 | go generate 30 | ``` 31 | 32 | ## Bump dependencies 33 | 34 | To bump all dependencies run: 35 | 36 | ```sh 37 | go get -u -t ./... 38 | go mod tidy 39 | ``` 40 | -------------------------------------------------------------------------------- /src/checkoutservice/kafka/producer.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | package kafka 4 | 5 | import ( 6 | "github.com/IBM/sarama" 7 | "github.com/sirupsen/logrus" 8 | ) 9 | 10 | var ( 11 | Topic = "orders" 12 | ProtocolVersion = sarama.V3_0_0_0 13 | ) 14 | 15 | func CreateKafkaProducer(brokers []string, log *logrus.Logger) (sarama.AsyncProducer, error) { 16 | saramaConfig := sarama.NewConfig() 17 | saramaConfig.Version = ProtocolVersion 18 | // So we can know the partition and offset of messages. 19 | saramaConfig.Producer.Return.Successes = true 20 | 21 | producer, err := sarama.NewAsyncProducer(brokers, saramaConfig) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | // We will log to STDOUT if we're not able to produce messages. 27 | go func() { 28 | for err := range producer.Errors() { 29 | log.Errorf("Failed to write message: %+v", err) 30 | } 31 | }() 32 | return producer, nil 33 | } 34 | -------------------------------------------------------------------------------- /src/checkoutservice/tools.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build tools 5 | // +build tools 6 | 7 | package tools 8 | 9 | // This file follows the recommendation at 10 | // https://go.dev/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module 11 | // on how to pin tooling dependencies to a go.mod file. 12 | // This ensures that all systems use the same version of tools in addition to regular dependencies. 13 | 14 | import ( 15 | _ "google.golang.org/grpc/cmd/protoc-gen-go-grpc" 16 | _ "google.golang.org/protobuf/cmd/protoc-gen-go" 17 | ) 18 | -------------------------------------------------------------------------------- /src/currencyservice/.dockerignore: -------------------------------------------------------------------------------- 1 | client.js 2 | node_modules/ 3 | build/ 4 | out/ 5 | -------------------------------------------------------------------------------- /src/currencyservice/README.md: -------------------------------------------------------------------------------- 1 | # Currency Service 2 | 3 | The Currency Service does the conversion from one currency to another. 4 | It is a C++ based service. 5 | 6 | ## Building docker image 7 | 8 | To build the currency service, run the following from root directory 9 | of opentelemetry-demo 10 | 11 | ```sh 12 | docker-compose build currencyservice 13 | ``` 14 | 15 | ## Run the service 16 | 17 | Execute the below command to run the service. 18 | 19 | ```sh 20 | docker-compose up currencyservice 21 | ``` 22 | 23 | ## Run the client 24 | 25 | currencyclient is a sample client which sends some request to currency 26 | service. To run the client, execute the below command. 27 | 28 | ```sh 29 | docker exec -it currencyclient 7000 30 | ``` 31 | 32 | '7000' is port where currencyservice listens to. 33 | -------------------------------------------------------------------------------- /src/currencyservice/data/currency_conversion.json: -------------------------------------------------------------------------------- 1 | { 2 | "EUR": "1.0", 3 | "USD": "1.1305", 4 | "JPY": "126.40", 5 | "BGN": "1.9558", 6 | "CZK": "25.592", 7 | "DKK": "7.4609", 8 | "GBP": "0.85970", 9 | "HUF": "315.51", 10 | "PLN": "4.2996", 11 | "RON": "4.7463", 12 | "SEK": "10.5375", 13 | "CHF": "1.1360", 14 | "ISK": "136.80", 15 | "NOK": "9.8040", 16 | "HRK": "7.4210", 17 | "RUB": "74.4208", 18 | "TRY": "6.1247", 19 | "AUD": "1.6072", 20 | "BRL": "4.2682", 21 | "CAD": "1.5128", 22 | "CNY": "7.5857", 23 | "HKD": "8.8743", 24 | "IDR": "15999.40", 25 | "ILS": "4.0875", 26 | "INR": "79.4320", 27 | "KRW": "1275.05", 28 | "MXN": "21.7999", 29 | "MYR": "4.6289", 30 | "NZD": "1.6679", 31 | "PHP": "59.083", 32 | "SGD": "1.5349", 33 | "THB": "36.012", 34 | "ZAR": "16.0583" 35 | } -------------------------------------------------------------------------------- /src/currencyservice/proto/grpc/health/v1/health.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The gRPC Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // The canonical version of this proto can be found at 16 | // https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto 17 | 18 | syntax = "proto3"; 19 | 20 | package grpc.health.v1; 21 | 22 | option csharp_namespace = "Grpc.Health.V1"; 23 | option go_package = "google.golang.org/grpc/health/grpc_health_v1"; 24 | option java_multiple_files = true; 25 | option java_outer_classname = "HealthProto"; 26 | option java_package = "io.grpc.health.v1"; 27 | 28 | message HealthCheckRequest { 29 | string service = 1; 30 | } 31 | 32 | message HealthCheckResponse { 33 | enum ServingStatus { 34 | UNKNOWN = 0; 35 | SERVING = 1; 36 | NOT_SERVING = 2; 37 | } 38 | ServingStatus status = 1; 39 | } 40 | 41 | service Health { 42 | rpc Check(HealthCheckRequest) returns (HealthCheckResponse); 43 | } 44 | -------------------------------------------------------------------------------- /src/currencyservice/src/logger_common.h: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | #include "opentelemetry/exporters/otlp/otlp_grpc_exporter_factory.h" 5 | #include "opentelemetry/logs/provider.h" 6 | #include "opentelemetry/sdk/logs/logger.h" 7 | #include "opentelemetry/sdk/logs/logger_provider_factory.h" 8 | #include "opentelemetry/sdk/logs/simple_log_record_processor_factory.h" 9 | #include "opentelemetry/sdk/logs/logger_context_factory.h" 10 | #include "opentelemetry/exporters/otlp/otlp_grpc_log_record_exporter_factory.h" 11 | 12 | using namespace std; 13 | namespace nostd = opentelemetry::nostd; 14 | namespace otlp = opentelemetry::exporter::otlp; 15 | namespace logs = opentelemetry::logs; 16 | namespace logs_sdk = opentelemetry::sdk::logs; 17 | 18 | namespace 19 | { 20 | void initLogger() { 21 | otlp::OtlpGrpcLogRecordExporterOptions loggerOptions; 22 | auto exporter = otlp::OtlpGrpcLogRecordExporterFactory::Create(loggerOptions); 23 | auto processor = logs_sdk::SimpleLogRecordProcessorFactory::Create(std::move(exporter)); 24 | std::vector> processors; 25 | processors.push_back(std::move(processor)); 26 | auto context = logs_sdk::LoggerContextFactory::Create(std::move(processors)); 27 | std::shared_ptr provider = logs_sdk::LoggerProviderFactory::Create(std::move(context)); 28 | opentelemetry::logs::Provider::SetLoggerProvider(provider); 29 | } 30 | 31 | nostd::shared_ptr getLogger(std::string name){ 32 | auto provider = logs::Provider::GetLoggerProvider(); 33 | return provider->GetLogger(name + "_logger", name, OPENTELEMETRY_SDK_VERSION); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/emailservice/.dockerignore: -------------------------------------------------------------------------------- 1 | README.md 2 | -------------------------------------------------------------------------------- /src/emailservice/.ruby-version: -------------------------------------------------------------------------------- 1 | 3.2.2 2 | -------------------------------------------------------------------------------- /src/emailservice/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright The OpenTelemetry Authors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | 5 | FROM ruby:3.2.2-slim as base 6 | 7 | FROM base as builder 8 | 9 | WORKDIR /tmp 10 | 11 | COPY Gemfile Gemfile.lock . 12 | 13 | #RUN apk update && apk add make gcc musl-dev gcompat && bundle install 14 | RUN apt-get update && apt-get install build-essential -y && bundle install 15 | FROM base as release 16 | 17 | WORKDIR /email_server 18 | 19 | COPY . . 20 | 21 | RUN chmod 666 ./Gemfile.lock 22 | 23 | COPY --from=builder /usr/local/bundle/ /usr/local/bundle/ 24 | 25 | 26 | EXPOSE ${EMAIL_SERVICE_PORT} 27 | ENTRYPOINT ["bundle", "exec", "ruby", "email_server.rb"] 28 | -------------------------------------------------------------------------------- /src/emailservice/Gemfile: -------------------------------------------------------------------------------- 1 | 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "net-smtp", "~> 0.3" 6 | gem "pony", "~> 1.13" 7 | gem "puma", "~> 6.3" 8 | gem "sinatra", "~> 3.0" 9 | 10 | gem "opentelemetry-sdk", "~> 1.2" 11 | gem "opentelemetry-exporter-otlp", "~> 0.24" 12 | gem "opentelemetry-instrumentation-all", "~> 0.39" 13 | -------------------------------------------------------------------------------- /src/emailservice/README.md: -------------------------------------------------------------------------------- 1 | # Email Service 2 | 3 | The Email service "sends" an email to the customer with their order details by 4 | rendering it as a log message. It expects a JSON payload like: 5 | 6 | ```json 7 | { 8 | "email": "some.address@website.com", 9 | "order": "" 10 | } 11 | ``` 12 | 13 | ## Local Build 14 | 15 | We use `bundler` to manage dependencies. To get started, simply `bundle install`. 16 | 17 | ## Running locally 18 | 19 | You may run this service locally with `bundle exec ruby email_server.rb`. 20 | 21 | ## Docker Build 22 | 23 | From `src/emailservice`, run `docker build .` 24 | -------------------------------------------------------------------------------- /src/emailservice/email_server.rb: -------------------------------------------------------------------------------- 1 | # Copyright The OpenTelemetry Authors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | require "ostruct" 5 | require "pony" 6 | require "sinatra" 7 | 8 | require "opentelemetry/sdk" 9 | require "opentelemetry/exporter/otlp" 10 | require "opentelemetry/instrumentation/sinatra" 11 | 12 | set :port, ENV["EMAIL_SERVICE_PORT"] 13 | 14 | OpenTelemetry::SDK.configure do |c| 15 | c.use "OpenTelemetry::Instrumentation::Sinatra" 16 | end 17 | 18 | post "/send_order_confirmation" do 19 | data = JSON.parse(request.body.read, object_class: OpenStruct) 20 | 21 | # get the current auto-instrumented span 22 | current_span = OpenTelemetry::Trace.current_span 23 | current_span.add_attributes({ 24 | "app.order.id" => data.order.order_id, 25 | }) 26 | 27 | send_email(data) 28 | 29 | end 30 | 31 | error do 32 | OpenTelemetry::Trace.current_span.record_exception(env['sinatra.error']) 33 | end 34 | 35 | def send_email(data) 36 | # create and start a manual span 37 | tracer = OpenTelemetry.tracer_provider.tracer('emailservice') 38 | tracer.in_span("send_email") do |span| 39 | Pony.mail( 40 | to: data.email, 41 | from: "noreply@example.com", 42 | subject: "Your confirmation email", 43 | body: erb(:confirmation, locals: { order: data.order }), 44 | via: :test 45 | ) 46 | span.set_attribute("app.email.recipient", data.email) 47 | puts "Order confirmation email sent to: #{data.email}" 48 | end 49 | # manually created spans need to be ended 50 | # in Ruby, the method `in_span` ends it automatically 51 | # check out the OpenTelemetry Ruby docs at: 52 | # https://opentelemetry.io/docs/instrumentation/ruby/manual/#creating-new-spans 53 | end 54 | -------------------------------------------------------------------------------- /src/frauddetectionservice/.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile -------------------------------------------------------------------------------- /src/frauddetectionservice/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright The OpenTelemetry Authors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | 5 | FROM gradle:8-jdk17 AS builder 6 | 7 | WORKDIR /usr/src/app/ 8 | 9 | COPY ./src/frauddetectionservice/ ./ 10 | COPY ./pb/ ./src/main/proto/ 11 | RUN gradle shadowJar 12 | 13 | # ----------------------------------------------------------------------------- 14 | 15 | FROM gcr.io/distroless/java17-debian11 16 | 17 | ARG version=2.3.0 18 | WORKDIR /usr/src/app/ 19 | 20 | COPY --from=builder /usr/src/app/build/libs/frauddetectionservice-1.0-all.jar ./ 21 | # use a fixed version 22 | # ADD https://github.com/grafana/grafana-opentelemetry-java/releases/download/v$version/grafana-opentelemetry-java.jar /usr/src/app/opentelemetry-javaagent.jar 23 | # use the latest version 24 | ADD --chmod=644 https://github.com/grafana/grafana-opentelemetry-java/releases/latest/download/grafana-opentelemetry-java.jar /usr/src/app/opentelemetry-javaagent.jar 25 | ENV JAVA_TOOL_OPTIONS=-javaagent:/usr/src/app/opentelemetry-javaagent.jar 26 | 27 | ENTRYPOINT [ "java", "-jar", "frauddetectionservice-1.0-all.jar" ] 28 | -------------------------------------------------------------------------------- /src/frauddetectionservice/README.md: -------------------------------------------------------------------------------- 1 | # Fraud Detection Service 2 | 3 | This service receives new orders by a Kafka topic and returns cases which are 4 | suspected of fraud. 5 | 6 | ## Local Build 7 | 8 | To build the protos and the service binary, run from the repo root: 9 | 10 | ```sh 11 | cp -r ../../pb/ src/main/proto/ 12 | ./gradlew shadowJar 13 | ``` 14 | 15 | ## Docker Build 16 | 17 | To build using Docker run from the repo root: 18 | 19 | ```sh 20 | docker build -f ./src/frauddetectionservice/Dockerfile . 21 | ``` 22 | -------------------------------------------------------------------------------- /src/frauddetectionservice/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | -------------------------------------------------------------------------------- /src/frauddetectionservice/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grafana/opentelemetry-demo/a635de2d949c1090075458ecbdb99fde24103693/src/frauddetectionservice/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/frauddetectionservice/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /src/frauddetectionservice/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | rootProject.name = "frauddetectionservice" 3 | 4 | -------------------------------------------------------------------------------- /src/frauddetectionservice/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /src/frontend/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:react/recommended", "plugin:@typescript-eslint/recommended", "next/core-web-vitals"], 3 | "plugins": ["@typescript-eslint"], 4 | "root": true, 5 | "globals": {}, 6 | "rules": { 7 | "@typescript-eslint/no-non-null-assertion": "off", 8 | "react-hooks/exhaustive-deps": "warn", 9 | "no-unused-vars": "off", 10 | "@typescript-eslint/no-unused-vars": "error", 11 | "max-len": [ 12 | "error", 13 | { 14 | "code": 150, 15 | "ignoreComments": true, 16 | "ignoreTrailingComments": true, 17 | "ignoreUrls": true, 18 | "ignoreStrings": true, 19 | "ignoreTemplateLiterals": true 20 | } 21 | ] 22 | }, 23 | "parser": "@typescript-eslint/parser", 24 | "env": {}, 25 | "overrides": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frontend/.prettierignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .git 3 | build 4 | dist 5 | .husky 6 | node_modules 7 | -------------------------------------------------------------------------------- /src/frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "importOrderSeparation": true, 3 | "importOrderSortSpecifiers": true, 4 | "singleQuote": true, 5 | "arrowParens": "avoid", 6 | "bracketSpacing": true, 7 | "semi": true, 8 | "trailingComma": "es5", 9 | "printWidth": 120, 10 | "jsxBracketSameLine": false, 11 | "proseWrap": "always", 12 | "quoteProps": "as-needed", 13 | "tabWidth": 2, 14 | "useTabs": false 15 | } 16 | -------------------------------------------------------------------------------- /src/frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine AS deps 2 | RUN apk add --no-cache libc6-compat 3 | 4 | WORKDIR /app 5 | 6 | COPY ./src/frontend/package*.json ./ 7 | RUN npm ci 8 | 9 | FROM node:18-alpine AS builder 10 | RUN apk add --no-cache libc6-compat protobuf-dev protoc 11 | WORKDIR /app 12 | COPY --from=deps /app/node_modules ./node_modules 13 | COPY ./pb ./pb 14 | COPY ./src/frontend . 15 | 16 | RUN npm run grpc:generate 17 | RUN npm run build 18 | 19 | FROM node:18-alpine AS runner 20 | WORKDIR /app 21 | RUN apk add --no-cache protobuf-dev protoc 22 | 23 | ENV NODE_ENV=production 24 | 25 | RUN addgroup -g 1001 -S nodejs 26 | RUN adduser -S nextjs -u 1001 27 | 28 | COPY --from=builder /app/next.config.js ./ 29 | COPY --from=builder /app/utils/telemetry/Instrumentation.js ./ 30 | COPY --from=builder /app/public ./public 31 | COPY --from=builder /app/package.json ./package.json 32 | COPY --from=deps /app/node_modules ./node_modules 33 | 34 | COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ 35 | COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static 36 | 37 | USER nextjs 38 | 39 | ENV PORT 8080 40 | EXPOSE ${PORT} 41 | 42 | ENTRYPOINT npm start 43 | -------------------------------------------------------------------------------- /src/frontend/Dockerfile.ci: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine AS build 2 | RUN apk add --no-cache libc6-compat 3 | WORKDIR /app 4 | COPY ./package*.json ./ 5 | RUN npm install && npm ci 6 | RUN apk add --no-cache libc6-compat protobuf-dev protoc 7 | COPY . . 8 | COPY ./pb ./pb 9 | RUN npm run grpc:generate 10 | RUN npm run build 11 | 12 | FROM node:18-alpine AS runner 13 | RUN apk add --no-cache protobuf-dev protoc 14 | WORKDIR /app 15 | ENV NODE_ENV=production 16 | RUN addgroup -g 1001 -S nodejs 17 | RUN adduser -S nextjs -u 1001 18 | 19 | COPY --from=build /app/next.config.js ./ 20 | COPY --from=build /app/utils/telemetry/Instrumentation.js ./ 21 | COPY --from=build /app/public ./public 22 | COPY --from=build /app/package.json ./package.json 23 | COPY --from=build /app/node_modules ./node_modules 24 | COPY --from=build --chown=nextjs:nodejs /app/.next/standalone ./ 25 | COPY --from=build --chown=nextjs:nodejs /app/.next/static ./.next/static 26 | 27 | USER nextjs 28 | 29 | ENV PORT 8080 30 | EXPOSE ${PORT} 31 | 32 | ENTRYPOINT npm start 33 | -------------------------------------------------------------------------------- /src/frontend/Dockerfile.cypress: -------------------------------------------------------------------------------- 1 | FROM cypress/included:10.3.1-typescript 2 | WORKDIR /app 3 | COPY ./src/frontend . 4 | RUN npm clean-install 5 | 6 | ENTRYPOINT ["cypress", "run"] 7 | -------------------------------------------------------------------------------- /src/frontend/README.md: -------------------------------------------------------------------------------- 1 | # Frontend service 2 | 3 | The frontend is a [Next.js](https://nextjs.org/) application that is composed 4 | by two layers. 5 | 6 | 1. Client side application. Which renders the components for the OTEL webstore. 7 | 2. API layer. Connects the client to the backend services by exposing REST endpoints. 8 | 9 | ## Build Locally 10 | 11 | By running `docker compose up` at the root of the project you'll have access to the 12 | frontend client by going to . 13 | 14 | ## Local development 15 | 16 | Currently, the easiest way to run the frontend for local development is to execute 17 | 18 | ```shell 19 | docker compose run --service-ports -e NODE_ENV=development --volume $(pwd)/src/frontend:/app --volume $(pwd)/pb:/app/pb --user node --entrypoint sh frontend 20 | ``` 21 | 22 | from the root folder. 23 | 24 | It will start all of the required backend services 25 | and within the container simply run `npm run dev`. 26 | After that the app should be available at . 27 | -------------------------------------------------------------------------------- /src/frontend/components/Ad/Ad.styled.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import styled from 'styled-components'; 5 | import RouterLink from 'next/link'; 6 | 7 | export const Ad = styled.section` 8 | position: relative; 9 | background-color: ${({ theme }) => theme.colors.otelYellow}; 10 | font-size: ${({ theme }) => theme.sizes.dMedium}; 11 | text-align: center; 12 | padding: 48px; 13 | 14 | * { 15 | color: ${({ theme }) => theme.colors.white}; 16 | margin: 0; 17 | cursor: pointer; 18 | } 19 | `; 20 | 21 | export const Link = styled(RouterLink)` 22 | color: black; 23 | `; 24 | -------------------------------------------------------------------------------- /src/frontend/components/Ad/Ad.tsx: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { CypressFields } from '../../utils/Cypress'; 5 | import { useAd } from '../../providers/Ad.provider'; 6 | import * as S from './Ad.styled'; 7 | 8 | const Ad = () => { 9 | const { adList } = useAd(); 10 | const { text, redirectUrl } = adList[Math.floor(Math.random() * adList.length)] || { text: '', redirectUrl: '' }; 11 | 12 | return ( 13 | 14 | 15 |

{text}

16 |
17 |
18 | ); 19 | }; 20 | 21 | export default Ad; 22 | -------------------------------------------------------------------------------- /src/frontend/components/Ad/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export { default } from './Ad'; 5 | -------------------------------------------------------------------------------- /src/frontend/components/Banner/Banner.styled.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import styled from 'styled-components'; 5 | import Button from '../Button'; 6 | 7 | export const Banner = styled.div` 8 | display: flex; 9 | flex-direction: column; 10 | 11 | ${({ theme }) => theme.breakpoints.desktop} { 12 | flex-direction: row-reverse; 13 | padding-bottom: 38px; 14 | background: ${({ theme }) => theme.colors.backgroundGray}; 15 | } 16 | `; 17 | 18 | export const BannerImg = styled.img.attrs({ 19 | src: '/images/Banner.png', 20 | })` 21 | width: 100%; 22 | height: auto; 23 | `; 24 | 25 | export const ImageContainer = styled.div` 26 | ${({ theme }) => theme.breakpoints.desktop} { 27 | min-width: 50%; 28 | } 29 | `; 30 | 31 | export const TextContainer = styled.div` 32 | padding: 20px; 33 | 34 | ${({ theme }) => theme.breakpoints.desktop} { 35 | display: flex; 36 | flex-direction: column; 37 | justify-content: center; 38 | align-items: start; 39 | width: 50%; 40 | padding: 100px 160px 100px 100px; 41 | } 42 | `; 43 | 44 | export const Title = styled.h1` 45 | font-size: ${({ theme }) => theme.sizes.mxLarge}; 46 | font-weight: ${({ theme }) => theme.fonts.bold}; 47 | 48 | ${({ theme }) => theme.breakpoints.desktop} { 49 | font-size: ${({ theme }) => theme.sizes.dxLarge}; 50 | } 51 | `; 52 | 53 | export const GoShoppingButton = styled(Button)` 54 | width: 100%; 55 | 56 | ${({ theme }) => theme.breakpoints.desktop} { 57 | width: auto; 58 | } 59 | `; 60 | -------------------------------------------------------------------------------- /src/frontend/components/Banner/Banner.tsx: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import Link from 'next/link'; 5 | import * as S from './Banner.styled'; 6 | 7 | const Banner = () => { 8 | return ( 9 | 10 | 11 | 12 | 13 | 14 | The best telescopes to see the world closer (now even more observant!) 15 | Go Shopping 16 | 17 | 18 | ); 19 | }; 20 | 21 | export default Banner; 22 | -------------------------------------------------------------------------------- /src/frontend/components/Banner/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export { default } from './Banner'; 5 | -------------------------------------------------------------------------------- /src/frontend/components/Button/Button.tsx: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import styled, { css } from 'styled-components'; 5 | 6 | const Button = styled.button<{ $type?: 'primary' | 'secondary' | 'link' }>` 7 | background-color: #5262a8; 8 | color: white; 9 | display: inline-block; 10 | border: solid 1px #5262a8; 11 | padding: 8px 16px; 12 | outline: none; 13 | font-weight: 700; 14 | font-size: 20px; 15 | line-height: 27px; 16 | border-radius: 10px; 17 | height: 62px; 18 | cursor: pointer; 19 | 20 | ${({ $type = 'primary' }) => 21 | $type === 'secondary' && 22 | css` 23 | background: none; 24 | color: #5262a8; 25 | `}; 26 | 27 | ${({ $type = 'primary' }) => 28 | $type === 'link' && 29 | css` 30 | background: none; 31 | color: #5262a8; 32 | border: none; 33 | `}; 34 | `; 35 | 36 | export default Button; 37 | -------------------------------------------------------------------------------- /src/frontend/components/Button/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export { default } from './Button'; 5 | -------------------------------------------------------------------------------- /src/frontend/components/Cart/EmptyCart.tsx: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import Link from 'next/link'; 5 | import Button from '../Button'; 6 | import * as S from '../../styles/Cart.styled'; 7 | 8 | const EmptyCart = () => { 9 | return ( 10 | 11 | Your shopping cart is empty! 12 | Items you add to your shopping cart will appear here. 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | }; 22 | 23 | export default EmptyCart; 24 | -------------------------------------------------------------------------------- /src/frontend/components/CartDropdown/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export { default } from './CartDropdown'; 5 | -------------------------------------------------------------------------------- /src/frontend/components/CartIcon/CartIcon.styled.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import Image from 'next/image'; 5 | import styled from 'styled-components'; 6 | 7 | export const CartIcon = styled.a` 8 | position: relative; 9 | display: block; 10 | margin-left: 25px; 11 | display: flex; 12 | flex-flow: column; 13 | align-items: center; 14 | justify-content: center; 15 | cursor: pointer; 16 | `; 17 | 18 | export const Icon = styled(Image).attrs({ 19 | width: '24px', 20 | height: '24px', 21 | })` 22 | margin-bottom: 3px; 23 | `; 24 | 25 | export const ItemsCount = styled.span` 26 | display: flex; 27 | align-items: center; 28 | justify-content: center; 29 | position: absolute; 30 | top: 9px; 31 | left: 15px; 32 | width: 15px; 33 | height: 15px; 34 | font-size: ${({ theme }) => theme.sizes.nano}; 35 | border-radius: 50%; 36 | border: 1px solid ${({ theme }) => theme.colors.white}; 37 | color: ${({ theme }) => theme.colors.white}; 38 | background: ${({ theme }) => theme.colors.otelRed}; 39 | `; 40 | -------------------------------------------------------------------------------- /src/frontend/components/CartIcon/CartIcon.tsx: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { useState } from 'react'; 5 | import { CypressFields } from '../../utils/Cypress'; 6 | import { useCart } from '../../providers/Cart.provider'; 7 | import CartDropdown from '../CartDropdown'; 8 | import * as S from './CartIcon.styled'; 9 | 10 | const CartIcon = () => { 11 | const [isOpen, setIsOpen] = useState(false); 12 | const { 13 | cart: { items }, 14 | } = useCart(); 15 | 16 | return ( 17 | <> 18 | setIsOpen(true)}> 19 | 20 | {!!items.length && {items.length}} 21 | 22 | setIsOpen(false)} /> 23 | 24 | ); 25 | }; 26 | 27 | export default CartIcon; 28 | -------------------------------------------------------------------------------- /src/frontend/components/CartIcon/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export { default } from './CartIcon'; 5 | -------------------------------------------------------------------------------- /src/frontend/components/CartItems/CartItem.tsx: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import Link from 'next/link'; 5 | import { Product } from '../../protos/demo'; 6 | import ProductPrice from '../ProductPrice'; 7 | import * as S from './CartItems.styled'; 8 | 9 | interface IProps { 10 | product: Product; 11 | quantity: number; 12 | } 13 | 14 | const CartItem = ({ 15 | product: { id, name, picture, priceUsd = { units: 0, nanos: 0, currencyCode: 'USD' } }, 16 | quantity, 17 | }: IProps) => { 18 | return ( 19 | 20 | 21 | 22 | 23 |

{name}

24 |
25 | 26 | 27 |

{quantity}

28 |
29 | 30 | 31 |

32 | 33 |

34 |
35 |
36 |
37 | ); 38 | }; 39 | 40 | export default CartItem; 41 | -------------------------------------------------------------------------------- /src/frontend/components/CartItems/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export { default } from './CartItems'; 5 | -------------------------------------------------------------------------------- /src/frontend/components/CheckoutForm/CheckoutForm.styled.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import styled from 'styled-components'; 5 | import Button from '../Button'; 6 | 7 | export const CheckoutForm = styled.form``; 8 | 9 | export const StateRow = styled.div` 10 | display: grid; 11 | grid-template-columns: 35% 55%; 12 | gap: 10%; 13 | `; 14 | 15 | export const Title = styled.h1` 16 | margin: 0; 17 | margin-bottom: 24px; 18 | `; 19 | 20 | export const CardRow = styled.div` 21 | display: grid; 22 | grid-template-columns: 35% 35% 20%; 23 | gap: 5%; 24 | `; 25 | 26 | export const SubmitContainer = styled.div` 27 | display: flex; 28 | justify-content: center; 29 | align-items: center; 30 | gap: 20px; 31 | flex-direction: column-reverse; 32 | 33 | ${({ theme }) => theme.breakpoints.desktop} { 34 | flex-direction: row; 35 | justify-content: end; 36 | align-items: center; 37 | margin-top: 67px; 38 | } 39 | `; 40 | 41 | export const CartButton = styled(Button)` 42 | padding: 16px 35px; 43 | font-weight: ${({ theme }) => theme.fonts.regular}; 44 | width: 100%; 45 | 46 | ${({ theme }) => theme.breakpoints.desktop} { 47 | width: inherit; 48 | } 49 | `; 50 | 51 | export const EmptyCartButton = styled(Button)` 52 | font-weight: ${({ theme }) => theme.fonts.regular}; 53 | color: ${({ theme }) => theme.colors.otelRed}; 54 | width: 100%; 55 | 56 | ${({ theme }) => theme.breakpoints.desktop} { 57 | width: inherit; 58 | } 59 | `; 60 | -------------------------------------------------------------------------------- /src/frontend/components/CheckoutForm/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export { default } from './CheckoutForm'; 5 | -------------------------------------------------------------------------------- /src/frontend/components/CheckoutItem/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export { default } from './CheckoutItem'; 5 | -------------------------------------------------------------------------------- /src/frontend/components/CurrencySwitcher/CurrencySwitcher.styled.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import styled from 'styled-components'; 5 | 6 | export const CurrencySwitcher = styled.div` 7 | display: flex; 8 | justify-content: flex-end; 9 | `; 10 | 11 | export const Container = styled.div` 12 | display: flex; 13 | align-items: center; 14 | position: relative; 15 | margin-left: 40px; 16 | color: #605f64; 17 | 18 | &::-webkit-input-placeholder, 19 | &::-moz-placeholder, 20 | :-ms-input-placeholder, 21 | :-moz-placeholder { 22 | font-size: 12px; 23 | color: #605f64; 24 | } 25 | `; 26 | 27 | export const SelectedConcurrency = styled.span` 28 | font-size: ${({ theme }) => theme.sizes.mLarge}; 29 | text-align: center; 30 | font-weight: ${({ theme }) => theme.fonts.regular}; 31 | 32 | position: relative; 33 | left: 35px; 34 | width: 20px; 35 | display: inline-block; 36 | `; 37 | 38 | export const Arrow = styled.img.attrs({ 39 | src: '/icons/Chevron.svg', 40 | alt: 'arrow', 41 | })` 42 | position: absolute; 43 | right: 15px; 44 | width: 12px; 45 | height: 17px; 46 | `; 47 | 48 | export const Select = styled.select` 49 | -webkit-appearance: none; 50 | -webkit-border-radius: 0px; 51 | font-size: ${({ theme }) => theme.sizes.mLarge}; 52 | cursor: pointer; 53 | 54 | display: flex; 55 | align-items: center; 56 | background: transparent; 57 | font-weight: ${({ theme }) => theme.fonts.regular}; 58 | border: 1px solid ${({ theme }) => theme.colors.borderGray}; 59 | width: 130px; 60 | height: 40px; 61 | flex-shrink: 0; 62 | padding: 1px 0 0 45px; 63 | font-size: 16px; 64 | border-radius: 10px; 65 | `; 66 | -------------------------------------------------------------------------------- /src/frontend/components/CurrencySwitcher/CurrencySwitcher.tsx: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { useMemo } from 'react'; 5 | import getSymbolFromCurrency from 'currency-symbol-map'; 6 | import { useCurrency } from '../../providers/Currency.provider'; 7 | import * as S from './CurrencySwitcher.styled'; 8 | import { CypressFields } from '../../utils/Cypress'; 9 | 10 | const CurrencySwitcher = () => { 11 | const { currencyCodeList, setSelectedCurrency, selectedCurrency } = useCurrency(); 12 | 13 | const currencySymbol = useMemo(() => getSymbolFromCurrency(selectedCurrency), [selectedCurrency]); 14 | 15 | return ( 16 | 17 | 18 | {currencySymbol} 19 | setSelectedCurrency(event.target.value)} 22 | value={selectedCurrency} 23 | data-cy={CypressFields.CurrencySwitcher} 24 | > 25 | {currencyCodeList.map(currencyCode => ( 26 | 29 | ))} 30 | 31 | 32 | 33 | 34 | ); 35 | }; 36 | 37 | export default CurrencySwitcher; 38 | -------------------------------------------------------------------------------- /src/frontend/components/CurrencySwitcher/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export { default } from './CurrencySwitcher'; 5 | -------------------------------------------------------------------------------- /src/frontend/components/Footer/Footer.styled.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import styled from 'styled-components'; 5 | 6 | export const Footer = styled.footer` 7 | position: relative; 8 | padding: 65px 9%; 9 | background-color: ${({ theme }) => theme.colors.otelGray}; 10 | 11 | * { 12 | color: ${({ theme }) => theme.colors.white}; 13 | font-size: ${({ theme }) => theme.sizes.dSmall}; 14 | font-weight: ${({ theme }) => theme.fonts.regular}; 15 | } 16 | `; 17 | -------------------------------------------------------------------------------- /src/frontend/components/Footer/Footer.tsx: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { useEffect, useState } from 'react'; 5 | import * as S from './Footer.styled'; 6 | import SessionGateway from '../../gateways/Session.gateway'; 7 | import { CypressFields } from '../../utils/Cypress'; 8 | import PlatformFlag from '../PlatformFlag'; 9 | 10 | const currentYear = new Date().getFullYear(); 11 | 12 | const { userId } = SessionGateway.getSession(); 13 | 14 | const Footer = () => { 15 | const [sessionId, setSessionId] = useState(''); 16 | 17 | useEffect(() => { 18 | setSessionId(userId); 19 | }, []); 20 | 21 | return ( 22 | 23 |
24 |

This website is hosted for demo purpose only. It is not an actual shop.

25 |

26 | session-id: {sessionId} 27 |

28 |
29 |

30 | @ {currentYear} OpenTelemetry (Source Code) 31 |

32 | 33 |
34 | ); 35 | }; 36 | 37 | export default Footer; 38 | -------------------------------------------------------------------------------- /src/frontend/components/Footer/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export { default } from './Footer'; 5 | -------------------------------------------------------------------------------- /src/frontend/components/Header/Header.styled.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import Link from 'next/link'; 5 | import styled from 'styled-components'; 6 | 7 | export const Header = styled.header` 8 | background-color: #853b5c; 9 | color: white; 10 | `; 11 | 12 | export const NavBar = styled.nav` 13 | height: 80px; 14 | background-color: white; 15 | font-size: 15px; 16 | color: #b4b2bb; 17 | border-bottom: 1px solid ${({ theme }) => theme.colors.textGray}; 18 | z-index: 1; 19 | padding: 0; 20 | 21 | ${({ theme }) => theme.breakpoints.desktop} { 22 | height: 100px; 23 | } 24 | `; 25 | 26 | export const Container = styled.div` 27 | display: flex; 28 | justify-content: space-between; 29 | align-items: center; 30 | width: 100%; 31 | height: 100%; 32 | padding: 0 20px; 33 | 34 | ${({ theme }) => theme.breakpoints.desktop} { 35 | padding: 25px 100px; 36 | } 37 | `; 38 | 39 | export const NavBarBrand = styled(Link)` 40 | display: flex; 41 | align-items: center; 42 | padding: 0; 43 | 44 | img { 45 | height: 30px; 46 | } 47 | `; 48 | 49 | export const BrandImg = styled.img.attrs({ 50 | src: '/images/opentelemetry-demo-logo.png', 51 | })` 52 | width: 280px; 53 | height: auto; 54 | `; 55 | 56 | export const Controls = styled.div` 57 | display: flex; 58 | height: 60px; 59 | `; 60 | -------------------------------------------------------------------------------- /src/frontend/components/Header/Header.tsx: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import CartIcon from '../CartIcon'; 5 | import CurrencySwitcher from '../CurrencySwitcher'; 6 | import * as S from './Header.styled'; 7 | 8 | const Header = () => { 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ); 24 | }; 25 | 26 | export default Header; 27 | -------------------------------------------------------------------------------- /src/frontend/components/Header/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export {default} from './Header'; 5 | -------------------------------------------------------------------------------- /src/frontend/components/Input/Input.styled.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import styled from 'styled-components'; 5 | 6 | export const Input = styled.input` 7 | width: -webkit-fill-available; 8 | border: none; 9 | padding: 16px; 10 | outline: none; 11 | 12 | font-weight: ${({ theme }) => theme.fonts.regular}; 13 | font-size: ${({ theme }) => theme.sizes.dMedium}; 14 | 15 | border-radius: 10px; 16 | background: #f9f9f9; 17 | border: 1px solid #cacaca; 18 | `; 19 | 20 | export const InputLabel = styled.p` 21 | font-size: ${({ theme }) => theme.sizes.dMedium}; 22 | font-weight: ${({ theme }) => theme.fonts.semiBold}; 23 | margin: 0; 24 | margin-bottom: 15px; 25 | `; 26 | 27 | export const Select = styled.select` 28 | width: 100%; 29 | border: none; 30 | 31 | padding: 16px; 32 | font-weight: ${({ theme }) => theme.fonts.regular}; 33 | font-size: ${({ theme }) => theme.sizes.dMedium}; 34 | 35 | border-radius: 10px; 36 | background: #f9f9f9; 37 | border: 1px solid #cacaca; 38 | `; 39 | 40 | export const InputRow = styled.div` 41 | position: relative; 42 | margin-bottom: 24px; 43 | `; 44 | 45 | export const Arrow = styled.img.attrs({ 46 | src: '/icons/Chevron.svg', 47 | alt: 'arrow', 48 | })` 49 | position: absolute; 50 | right: 20px; 51 | width: 10px; 52 | height: 5px; 53 | top: 64px; 54 | `; 55 | -------------------------------------------------------------------------------- /src/frontend/components/Input/Input.tsx: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { HTMLInputTypeAttribute, InputHTMLAttributes } from 'react'; 5 | import * as S from './Input.styled'; 6 | 7 | interface IProps extends InputHTMLAttributes { 8 | type: HTMLInputTypeAttribute | 'select'; 9 | children?: React.ReactNode; 10 | label: string; 11 | } 12 | 13 | const Input = ({ type, id = '', children, label, ...props }: IProps) => { 14 | return ( 15 | 16 | {label} 17 | {type === 'select' ? ( 18 | <> 19 | 20 | {children} 21 | 22 | 23 | 24 | ) : ( 25 | 26 | )} 27 | 28 | ); 29 | }; 30 | 31 | export default Input; 32 | -------------------------------------------------------------------------------- /src/frontend/components/Input/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export {default} from './Input'; 5 | -------------------------------------------------------------------------------- /src/frontend/components/Layout/Layout.tsx: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import Header from '../Header'; 5 | 6 | interface IProps { 7 | children: React.ReactNode; 8 | } 9 | 10 | const Layout = ({ children }: IProps) => { 11 | return ( 12 | <> 13 |
14 |
{children}
15 | 16 | ); 17 | }; 18 | 19 | export default Layout; 20 | -------------------------------------------------------------------------------- /src/frontend/components/Layout/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export {default} from './Layout'; 5 | -------------------------------------------------------------------------------- /src/frontend/components/PlatformFlag/PlatformFlag.styled.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import styled from 'styled-components'; 5 | 6 | export const Block = styled.div` 7 | position: absolute; 8 | bottom: 0; 9 | right: 0; 10 | width: 100px; 11 | height: 27px; 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | font-size: ${({ theme }) => theme.sizes.mSmall}; 16 | font-weight: ${({ theme }) => theme.fonts.regular}; 17 | color: ${({ theme }) => theme.colors.white}; 18 | background: ${({ theme }) => theme.colors.otelYellow}; 19 | 20 | ${({ theme }) => theme.breakpoints.desktop} { 21 | width: 190px; 22 | height: 50px; 23 | font-size: ${({ theme }) => theme.sizes.dSmall}; 24 | } 25 | `; 26 | -------------------------------------------------------------------------------- /src/frontend/components/PlatformFlag/PlatformFlag.tsx: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import * as S from './PlatformFlag.styled'; 5 | 6 | const { NEXT_PUBLIC_PLATFORM = 'local' } = typeof window !== 'undefined' ? window.ENV : {}; 7 | 8 | const platform = NEXT_PUBLIC_PLATFORM; 9 | 10 | const PlatformFlag = () => { 11 | return ( 12 | {platform} 13 | ); 14 | }; 15 | 16 | export default PlatformFlag; 17 | -------------------------------------------------------------------------------- /src/frontend/components/PlatformFlag/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export { default } from './PlatformFlag'; 5 | -------------------------------------------------------------------------------- /src/frontend/components/ProductCard/ProductCard.styled.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import styled from 'styled-components'; 5 | import RouterLink from 'next/link'; 6 | 7 | export const Link = styled(RouterLink)``; 8 | 9 | export const Image = styled.div<{ $src: string }>` 10 | width: 100%; 11 | height: 150px; 12 | background: url(${({ $src }) => $src}) no-repeat center; 13 | background-size: 100% auto; 14 | 15 | ${({ theme }) => theme.breakpoints.desktop} { 16 | height: 300px; 17 | } 18 | `; 19 | 20 | export const ProductCard = styled.div` 21 | cursor: pointer; 22 | `; 23 | 24 | export const ProductName = styled.p` 25 | margin: 0; 26 | margin-top: 10px; 27 | font-size: ${({ theme }) => theme.sizes.dSmall}; 28 | `; 29 | 30 | export const ProductPrice = styled.p` 31 | margin: 0; 32 | font-size: ${({ theme }) => theme.sizes.dMedium}; 33 | font-weight: ${({ theme }) => theme.fonts.bold}; 34 | `; 35 | -------------------------------------------------------------------------------- /src/frontend/components/ProductCard/ProductCard.tsx: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { CypressFields } from '../../utils/Cypress'; 5 | import { Product } from '../../protos/demo'; 6 | import ProductPrice from '../ProductPrice'; 7 | import * as S from './ProductCard.styled'; 8 | 9 | interface IProps { 10 | product: Product; 11 | } 12 | 13 | const ProductCard = ({ 14 | product: { 15 | id, 16 | picture, 17 | name, 18 | priceUsd = { 19 | currencyCode: 'USD', 20 | units: 0, 21 | nanos: 0, 22 | }, 23 | }, 24 | }: IProps) => { 25 | return ( 26 | 27 | 28 | 29 |
30 | {name} 31 | 32 | 33 | 34 |
35 |
36 |
37 | ); 38 | }; 39 | 40 | export default ProductCard; 41 | -------------------------------------------------------------------------------- /src/frontend/components/ProductCard/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export { default } from './ProductCard'; 5 | -------------------------------------------------------------------------------- /src/frontend/components/ProductList/ProductList.styled.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import styled from 'styled-components'; 5 | 6 | export const ProductList = styled.div` 7 | display: grid; 8 | grid-template-columns: 1fr; 9 | gap: 24px; 10 | 11 | ${({ theme }) => theme.breakpoints.desktop} { 12 | grid-template-columns: repeat(3, auto); 13 | } 14 | `; 15 | -------------------------------------------------------------------------------- /src/frontend/components/ProductList/ProductList.tsx: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { CypressFields } from '../../utils/Cypress'; 5 | import { Product } from '../../protos/demo'; 6 | import ProductCard from '../ProductCard'; 7 | import * as S from './ProductList.styled'; 8 | 9 | interface IProps { 10 | productList: Product[]; 11 | } 12 | 13 | const ProductList = ({ productList }: IProps) => { 14 | return ( 15 | 16 | {productList.map(product => ( 17 | 18 | ))} 19 | 20 | ); 21 | }; 22 | 23 | export default ProductList; 24 | -------------------------------------------------------------------------------- /src/frontend/components/ProductList/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export { default } from './ProductList'; 5 | -------------------------------------------------------------------------------- /src/frontend/components/ProductPrice/ProductPrice.tsx: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { useMemo } from 'react'; 5 | import getSymbolFromCurrency from 'currency-symbol-map'; 6 | import { Money } from '../../protos/demo'; 7 | import { useCurrency } from '../../providers/Currency.provider'; 8 | import { CypressFields } from '../../utils/Cypress'; 9 | 10 | interface IProps { 11 | price: Money; 12 | } 13 | 14 | const ProductPrice = ({ price: { units, currencyCode, nanos } }: IProps) => { 15 | const { selectedCurrency } = useCurrency(); 16 | 17 | const currencySymbol = useMemo( 18 | () => getSymbolFromCurrency(currencyCode) || selectedCurrency, 19 | [currencyCode, selectedCurrency] 20 | ); 21 | 22 | const total = units + nanos / 1000000000; 23 | 24 | return ( 25 | 26 | {currencySymbol} {total.toFixed(2)} 27 | 28 | ); 29 | }; 30 | 31 | export default ProductPrice; 32 | -------------------------------------------------------------------------------- /src/frontend/components/ProductPrice/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export { default } from './ProductPrice'; 5 | -------------------------------------------------------------------------------- /src/frontend/components/Recommendations/Recommendations.styled.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import styled from 'styled-components'; 5 | 6 | export const Recommendations = styled.section` 7 | display: flex; 8 | margin: 40px 0; 9 | align-items: center; 10 | flex-direction: column; 11 | `; 12 | 13 | export const ProductList = styled.div` 14 | display: flex; 15 | width: 100%; 16 | padding: 0 20px; 17 | flex-direction: column; 18 | gap: 24px; 19 | 20 | ${({ theme }) => theme.breakpoints.desktop} { 21 | display: grid; 22 | grid-template-columns: 1fr 1fr 1fr 1fr; 23 | } 24 | `; 25 | 26 | export const TitleContainer = styled.div` 27 | border-top: 1px dashed; 28 | padding: 40px 0; 29 | text-align: center; 30 | width: 100%; 31 | `; 32 | 33 | export const Title = styled.h3` 34 | font-size: ${({ theme }) => theme.sizes.mLarge}; 35 | 36 | ${({ theme }) => theme.breakpoints.desktop} { 37 | font-size: ${({ theme }) => theme.sizes.dLarge}; 38 | } 39 | `; 40 | -------------------------------------------------------------------------------- /src/frontend/components/Recommendations/Recommendations.tsx: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { CypressFields } from '../../utils/Cypress'; 5 | import { useAd } from '../../providers/Ad.provider'; 6 | import ProductCard from '../ProductCard'; 7 | import * as S from './Recommendations.styled'; 8 | 9 | const Recommendations = () => { 10 | const { recommendedProductList } = useAd(); 11 | 12 | return ( 13 | 14 | 15 | You May Also Like 16 | 17 | 18 | {recommendedProductList.map(product => ( 19 | 20 | ))} 21 | 22 | 23 | ); 24 | }; 25 | 26 | export default Recommendations; 27 | -------------------------------------------------------------------------------- /src/frontend/components/Recommendations/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export { default } from './Recommendations'; 5 | -------------------------------------------------------------------------------- /src/frontend/components/Select/Select.styled.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import styled from 'styled-components'; 5 | 6 | export const Select = styled.select` 7 | width: 100%; 8 | height: 45px; 9 | border: 1px solid ${({ theme }) => theme.colors.borderGray}; 10 | padding: 10px 16px; 11 | border-radius: 8px; 12 | position: relative; 13 | width: 100px; 14 | cursor: pointer; 15 | `; 16 | 17 | export const SelectContainer = styled.div` 18 | position: relative; 19 | width: min-content; 20 | `; 21 | 22 | export const Arrow = styled.img.attrs({ 23 | src: '/icons/Chevron.svg', 24 | alt: 'select', 25 | })` 26 | position: absolute; 27 | right: 25px; 28 | top: 20px; 29 | width: 10px; 30 | height: 5px; 31 | `; 32 | -------------------------------------------------------------------------------- /src/frontend/components/Select/Select.tsx: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { InputHTMLAttributes } from 'react'; 5 | import * as S from './Select.styled'; 6 | 7 | interface IProps extends InputHTMLAttributes { 8 | children: React.ReactNode; 9 | } 10 | 11 | const Select = ({ children, ...props }: IProps) => { 12 | return ( 13 | 14 | {children} 15 | 16 | 17 | ); 18 | }; 19 | 20 | export default Select; 21 | -------------------------------------------------------------------------------- /src/frontend/components/Select/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export { default } from './Select'; 5 | -------------------------------------------------------------------------------- /src/frontend/cypress.config.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { defineConfig } from 'cypress'; 5 | import dotEnv from 'dotenv'; 6 | import dotenvExpand from 'dotenv-expand'; 7 | import { resolve } from 'path'; 8 | 9 | const myEnv = dotEnv.config({ 10 | path: resolve(__dirname, '../../.env'), 11 | }); 12 | dotenvExpand.expand(myEnv); 13 | 14 | const { FRONTEND_ADDR = '', NODE_ENV, FRONTEND_PORT = '8080' } = process.env; 15 | 16 | const baseUrl = NODE_ENV === 'production' ? `http://${FRONTEND_ADDR}` : `http://localhost:${FRONTEND_PORT}`; 17 | 18 | export default defineConfig({ 19 | env: { 20 | baseUrl, 21 | }, 22 | e2e: { 23 | baseUrl, 24 | setupNodeEvents(on, config) { 25 | // implement node event listeners here 26 | }, 27 | supportFile: false, 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /src/frontend/cypress/e2e/Home.cy.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import getSymbolFromCurrency from 'currency-symbol-map'; 5 | import SessionGateway from '../../gateways/Session.gateway'; 6 | import { CypressFields, getElementByField } from '../../utils/Cypress'; 7 | 8 | describe('Home Page', () => { 9 | beforeEach(() => { 10 | cy.visit('/'); 11 | }); 12 | 13 | it('should validate the home page', () => { 14 | getElementByField(CypressFields.HomePage).should('exist'); 15 | getElementByField(CypressFields.ProductCard, getElementByField(CypressFields.ProductList)).should('have.length', 10); 16 | 17 | getElementByField(CypressFields.SessionId).should('contain', SessionGateway.getSession().userId); 18 | }); 19 | 20 | it('should change currency', () => { 21 | getElementByField(CypressFields.CurrencySwitcher).select('EUR'); 22 | getElementByField(CypressFields.ProductCard, getElementByField(CypressFields.ProductList)).should('have.length', 10); 23 | 24 | getElementByField(CypressFields.CurrencySwitcher).should('have.value', 'EUR'); 25 | 26 | getElementByField(CypressFields.ProductCard).should('contain', getSymbolFromCurrency('EUR')); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/frontend/gateways/Session.gateway.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { v4 } from 'uuid'; 5 | 6 | interface ISession { 7 | userId: string; 8 | currencyCode: string; 9 | } 10 | 11 | const sessionKey = 'session'; 12 | const defaultSession = { 13 | userId: v4(), 14 | currencyCode: 'USD', 15 | }; 16 | 17 | const SessionGateway = () => ({ 18 | getSession(): ISession { 19 | if (typeof window === 'undefined') return defaultSession; 20 | const sessionString = localStorage.getItem(sessionKey); 21 | 22 | if (!sessionString) localStorage.setItem(sessionKey, JSON.stringify(defaultSession)); 23 | 24 | return JSON.parse(sessionString || JSON.stringify(defaultSession)) as ISession; 25 | }, 26 | setSessionValue(key: K, value: ISession[K]) { 27 | const session = this.getSession(); 28 | 29 | localStorage.setItem(sessionKey, JSON.stringify({ ...session, [key]: value })); 30 | }, 31 | }); 32 | 33 | export default SessionGateway(); 34 | -------------------------------------------------------------------------------- /src/frontend/gateways/rpc/Ad.gateway.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { ChannelCredentials } from '@grpc/grpc-js'; 5 | import { AdResponse, AdServiceClient } from '../../protos/demo'; 6 | 7 | const { AD_SERVICE_ADDR = '' } = process.env; 8 | 9 | const client = new AdServiceClient(AD_SERVICE_ADDR, ChannelCredentials.createInsecure()); 10 | 11 | const AdGateway = () => ({ 12 | listAds(contextKeys: string[]) { 13 | return new Promise((resolve, reject) => 14 | client.getAds({ contextKeys: contextKeys }, (error, response) => (error ? reject(error) : resolve(response))) 15 | ); 16 | }, 17 | }); 18 | 19 | export default AdGateway(); 20 | -------------------------------------------------------------------------------- /src/frontend/gateways/rpc/Cart.gateway.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { ChannelCredentials } from '@grpc/grpc-js'; 5 | import { Cart, CartItem, CartServiceClient, Empty } from '../../protos/demo'; 6 | 7 | const { CART_SERVICE_ADDR = '' } = process.env; 8 | 9 | const client = new CartServiceClient(CART_SERVICE_ADDR, ChannelCredentials.createInsecure()); 10 | 11 | const CartGateway = () => ({ 12 | getCart(userId: string) { 13 | return new Promise((resolve, reject) => 14 | client.getCart({ userId }, (error, response) => (error ? reject(error) : resolve(response))) 15 | ); 16 | }, 17 | addItem(userId: string, item: CartItem) { 18 | return new Promise((resolve, reject) => 19 | client.addItem({ userId, item }, (error, response) => (error ? reject(error) : resolve(response))) 20 | ); 21 | }, 22 | emptyCart(userId: string) { 23 | return new Promise((resolve, reject) => 24 | client.emptyCart({ userId }, (error, response) => (error ? reject(error) : resolve(response))) 25 | ); 26 | }, 27 | }); 28 | 29 | export default CartGateway(); 30 | -------------------------------------------------------------------------------- /src/frontend/gateways/rpc/Checkout.gateway.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { ChannelCredentials } from '@grpc/grpc-js'; 5 | import { CheckoutServiceClient, PlaceOrderRequest, PlaceOrderResponse } from '../../protos/demo'; 6 | 7 | const { CHECKOUT_SERVICE_ADDR = '' } = process.env; 8 | 9 | const client = new CheckoutServiceClient(CHECKOUT_SERVICE_ADDR, ChannelCredentials.createInsecure()); 10 | 11 | const CheckoutGateway = () => ({ 12 | placeOrder(order: PlaceOrderRequest) { 13 | return new Promise((resolve, reject) => 14 | client.placeOrder(order, (error, response) => (error ? reject(error) : resolve(response))) 15 | ); 16 | }, 17 | }); 18 | 19 | export default CheckoutGateway(); 20 | -------------------------------------------------------------------------------- /src/frontend/gateways/rpc/Currency.gateway.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { ChannelCredentials } from '@grpc/grpc-js'; 5 | import { GetSupportedCurrenciesResponse, CurrencyServiceClient, Money } from '../../protos/demo'; 6 | 7 | const { CURRENCY_SERVICE_ADDR = '' } = process.env; 8 | 9 | const client = new CurrencyServiceClient(CURRENCY_SERVICE_ADDR, ChannelCredentials.createInsecure()); 10 | 11 | const CurrencyGateway = () => ({ 12 | convert(from: Money, toCode: string) { 13 | return new Promise((resolve, reject) => 14 | client.convert({ from, toCode }, (error, response) => (error ? reject(error) : resolve(response))) 15 | ); 16 | }, 17 | getSupportedCurrencies() { 18 | return new Promise((resolve, reject) => 19 | client.getSupportedCurrencies({}, (error, response) => (error ? reject(error) : resolve(response))) 20 | ); 21 | }, 22 | }); 23 | 24 | export default CurrencyGateway(); 25 | -------------------------------------------------------------------------------- /src/frontend/gateways/rpc/ProductCatalog.gateway.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { ChannelCredentials } from '@grpc/grpc-js'; 5 | import { ListProductsResponse, Product, ProductCatalogServiceClient } from '../../protos/demo'; 6 | 7 | const { PRODUCT_CATALOG_SERVICE_ADDR = '' } = process.env; 8 | 9 | const client = new ProductCatalogServiceClient(PRODUCT_CATALOG_SERVICE_ADDR, ChannelCredentials.createInsecure()); 10 | 11 | const ProductCatalogGateway = () => ({ 12 | listProducts() { 13 | return new Promise((resolve, reject) => 14 | client.listProducts({}, (error, response) => (error ? reject(error) : resolve(response))) 15 | ); 16 | }, 17 | getProduct(id: string) { 18 | return new Promise((resolve, reject) => 19 | client.getProduct({ id }, (error, response) => (error ? reject(error) : resolve(response))) 20 | ); 21 | }, 22 | }); 23 | 24 | export default ProductCatalogGateway(); 25 | -------------------------------------------------------------------------------- /src/frontend/gateways/rpc/Recommendations.gateway.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { ChannelCredentials } from '@grpc/grpc-js'; 5 | import { ListRecommendationsResponse, RecommendationServiceClient } from '../../protos/demo'; 6 | 7 | const { RECOMMENDATION_SERVICE_ADDR = '' } = process.env; 8 | 9 | const client = new RecommendationServiceClient(RECOMMENDATION_SERVICE_ADDR, ChannelCredentials.createInsecure()); 10 | 11 | const RecommendationsGateway = () => ({ 12 | listRecommendations(userId: string, productIds: string[]) { 13 | return new Promise((resolve, reject) => 14 | client.listRecommendations({ userId, productIds }, (error, response) => 15 | error ? reject(error) : resolve(response) 16 | ) 17 | ); 18 | }, 19 | }); 20 | 21 | export default RecommendationsGateway(); 22 | -------------------------------------------------------------------------------- /src/frontend/gateways/rpc/Shipping.gateway.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { ChannelCredentials } from '@grpc/grpc-js'; 5 | import { Address, CartItem, GetQuoteResponse, ShippingServiceClient } from '../../protos/demo'; 6 | 7 | const { SHIPPING_SERVICE_ADDR = '' } = process.env; 8 | 9 | const client = new ShippingServiceClient(SHIPPING_SERVICE_ADDR, ChannelCredentials.createInsecure()); 10 | 11 | const ShippingGateway = () => ({ 12 | getShippingCost(itemList: CartItem[], address: Address) { 13 | return new Promise((resolve, reject) => 14 | client.getQuote({ items: itemList, address: address }, (error, response) => 15 | error ? reject(error) : resolve(response) 16 | ) 17 | ); 18 | }, 19 | }); 20 | 21 | export default ShippingGateway(); 22 | -------------------------------------------------------------------------------- /src/frontend/middleware.ts: -------------------------------------------------------------------------------- 1 | import {NextRequest, NextResponse} from 'next/server' 2 | import { trace } from '@opentelemetry/api' 3 | 4 | export function middleware(request: NextRequest) { 5 | const response = NextResponse.next() 6 | const current = trace.getActiveSpan(); 7 | 8 | // set server-timing header with traceparent 9 | if (current) { 10 | response.headers.set('server-timing', `traceparent;desc="00-${current.spanContext().traceId}-${current.spanContext().spanId}-01"`) 11 | } 12 | return response 13 | } 14 | -------------------------------------------------------------------------------- /src/frontend/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import '../styles/globals.css'; 5 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; 6 | import App, { AppContext, AppProps } from 'next/app'; 7 | import { getCookie } from 'cookies-next'; 8 | import CurrencyProvider from '../providers/Currency.provider'; 9 | import CartProvider from '../providers/Cart.provider'; 10 | import { ThemeProvider } from 'styled-components'; 11 | import Theme from '../styles/Theme'; 12 | import Faro from '../utils/telemetry/FaroTracer'; 13 | 14 | declare global { 15 | interface Window { 16 | ENV: { 17 | NEXT_PUBLIC_PLATFORM?: string; 18 | NEXT_PUBLIC_OTEL_SERVICE_NAME?: string; 19 | NEXT_PUBLIC_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT?: string; 20 | NEXT_GRAFANA_FARO_ENDPOINT?: string; 21 | IS_SYNTHETIC_REQUEST?: string; 22 | }; 23 | } 24 | } 25 | 26 | if (typeof window !== 'undefined') { 27 | const collector = getCookie('faroCollectorUrl')?.toString() || ''; 28 | Faro(collector); 29 | } 30 | 31 | const queryClient = new QueryClient(); 32 | 33 | function MyApp({ Component, pageProps }: AppProps) { 34 | return ( 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | ); 45 | } 46 | 47 | MyApp.getInitialProps = async (appContext: AppContext) => { 48 | const appProps = await App.getInitialProps(appContext); 49 | 50 | return { ...appProps }; 51 | }; 52 | 53 | export default MyApp; 54 | -------------------------------------------------------------------------------- /src/frontend/pages/api/checkout.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { NextApiRequest, NextApiResponse } from 'next'; 5 | import InstrumentationMiddleware from '../../utils/telemetry/InstrumentationMiddleware'; 6 | import CheckoutGateway from '../../gateways/rpc/Checkout.gateway'; 7 | import { Empty, PlaceOrderRequest } from '../../protos/demo'; 8 | import { IProductCheckoutItem, IProductCheckout } from '../../types/Cart'; 9 | import ProductCatalogService from '../../services/ProductCatalog.service'; 10 | 11 | type TResponse = IProductCheckout | Empty; 12 | 13 | const handler = async ({ method, body, query }: NextApiRequest, res: NextApiResponse) => { 14 | switch (method) { 15 | case 'POST': { 16 | const { currencyCode = '' } = query; 17 | const orderData = body as PlaceOrderRequest; 18 | const { order: { items = [], ...order } = {} } = await CheckoutGateway.placeOrder(orderData); 19 | 20 | const productList: IProductCheckoutItem[] = await Promise.all( 21 | items.map(async ({ item: { productId = '', quantity = 0 } = {}, cost }) => { 22 | const product = await ProductCatalogService.getProduct(productId, currencyCode as string); 23 | 24 | return { 25 | cost, 26 | item: { 27 | productId, 28 | quantity, 29 | product, 30 | }, 31 | }; 32 | }) 33 | ); 34 | 35 | return res.status(200).json({ ...order, items: productList }); 36 | } 37 | 38 | default: { 39 | return res.status(405).send(''); 40 | } 41 | } 42 | }; 43 | 44 | export default InstrumentationMiddleware(handler); 45 | -------------------------------------------------------------------------------- /src/frontend/pages/api/currency.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { NextApiRequest, NextApiResponse } from 'next'; 5 | import InstrumentationMiddleware from '../../utils/telemetry/InstrumentationMiddleware'; 6 | import CurrencyGateway from '../../gateways/rpc/Currency.gateway'; 7 | import { Empty } from '../../protos/demo'; 8 | 9 | type TResponse = string[] | Empty; 10 | 11 | const handler = async ({ method }: NextApiRequest, res: NextApiResponse) => { 12 | switch (method) { 13 | case 'GET': { 14 | const { currencyCodes = [] } = await CurrencyGateway.getSupportedCurrencies(); 15 | 16 | return res.status(200).json(currencyCodes); 17 | } 18 | 19 | default: { 20 | return res.status(405); 21 | } 22 | } 23 | }; 24 | 25 | export default InstrumentationMiddleware(handler); 26 | -------------------------------------------------------------------------------- /src/frontend/pages/api/data.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { NextApiRequest, NextApiResponse } from 'next'; 5 | import InstrumentationMiddleware from '../../utils/telemetry/InstrumentationMiddleware'; 6 | import AdGateway from '../../gateways/rpc/Ad.gateway'; 7 | import { Ad, Empty } from '../../protos/demo'; 8 | 9 | type TResponse = Ad[] | Empty; 10 | 11 | const handler = async ({ method, query }: NextApiRequest, res: NextApiResponse) => { 12 | switch (method) { 13 | case 'GET': { 14 | const { contextKeys = [] } = query; 15 | const { ads: adList } = await AdGateway.listAds(Array.isArray(contextKeys) ? contextKeys : contextKeys.split(',')); 16 | 17 | return res.status(200).json(adList); 18 | } 19 | 20 | default: { 21 | return res.status(405).send(''); 22 | } 23 | } 24 | }; 25 | 26 | export default InstrumentationMiddleware(handler); 27 | -------------------------------------------------------------------------------- /src/frontend/pages/api/products/[productId]/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { NextApiRequest, NextApiResponse } from 'next'; 5 | import InstrumentationMiddleware from '../../../../utils/telemetry/InstrumentationMiddleware'; 6 | import { Empty, Product } from '../../../../protos/demo'; 7 | import ProductCatalogService from '../../../../services/ProductCatalog.service'; 8 | 9 | type TResponse = Product | Empty; 10 | 11 | const handler = async ({ method, query }: NextApiRequest, res: NextApiResponse) => { 12 | switch (method) { 13 | case 'GET': { 14 | const { productId = '', currencyCode = '' } = query; 15 | const product = await ProductCatalogService.getProduct(productId as string, currencyCode as string); 16 | 17 | return res.status(200).json(product); 18 | } 19 | 20 | default: { 21 | return res.status(405).send(''); 22 | } 23 | } 24 | }; 25 | 26 | export default InstrumentationMiddleware(handler); 27 | -------------------------------------------------------------------------------- /src/frontend/pages/api/products/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { NextApiRequest, NextApiResponse } from 'next'; 5 | import InstrumentationMiddleware from '../../../utils/telemetry/InstrumentationMiddleware'; 6 | import { Empty, Product } from '../../../protos/demo'; 7 | import ProductCatalogService from '../../../services/ProductCatalog.service'; 8 | 9 | type TResponse = Product[] | Empty; 10 | 11 | const handler = async ({ method, query }: NextApiRequest, res: NextApiResponse) => { 12 | switch (method) { 13 | case 'GET': { 14 | const { currencyCode = '' } = query; 15 | const productList = await ProductCatalogService.listProducts(currencyCode as string); 16 | 17 | return res.status(200).json(productList); 18 | } 19 | 20 | default: { 21 | return res.status(405).send(''); 22 | } 23 | } 24 | }; 25 | 26 | export default InstrumentationMiddleware(handler); 27 | -------------------------------------------------------------------------------- /src/frontend/pages/api/recommendations.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { NextApiRequest, NextApiResponse } from 'next'; 5 | import InstrumentationMiddleware from '../../utils/telemetry/InstrumentationMiddleware'; 6 | import RecommendationsGateway from '../../gateways/rpc/Recommendations.gateway'; 7 | import { Empty, Product } from '../../protos/demo'; 8 | import ProductCatalogService from '../../services/ProductCatalog.service'; 9 | 10 | type TResponse = Product[] | Empty; 11 | 12 | const handler = async ({ method, query }: NextApiRequest, res: NextApiResponse) => { 13 | switch (method) { 14 | case 'GET': { 15 | const { productIds = [], sessionId = '', currencyCode = '' } = query; 16 | const { productIds: productList } = await RecommendationsGateway.listRecommendations( 17 | sessionId as string, 18 | productIds as string[] 19 | ); 20 | const recommendedProductList = await Promise.all( 21 | productList.slice(0, 4).map(id => ProductCatalogService.getProduct(id, currencyCode as string)) 22 | ); 23 | 24 | return res.status(200).json(recommendedProductList); 25 | } 26 | 27 | default: { 28 | return res.status(405).send(''); 29 | } 30 | } 31 | }; 32 | 33 | export default InstrumentationMiddleware(handler); 34 | -------------------------------------------------------------------------------- /src/frontend/pages/api/shipping.ts: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { NextApiRequest, NextApiResponse } from 'next'; 5 | import InstrumentationMiddleware from '../../utils/telemetry/InstrumentationMiddleware'; 6 | import ShippingGateway from '../../gateways/rpc/Shipping.gateway'; 7 | import { Address, CartItem, Empty, Money } from '../../protos/demo'; 8 | import CurrencyGateway from '../../gateways/rpc/Currency.gateway'; 9 | 10 | type TResponse = Money | Empty; 11 | 12 | const handler = async ({ method, query }: NextApiRequest, res: NextApiResponse) => { 13 | switch (method) { 14 | case 'GET': { 15 | const { itemList = '', currencyCode = 'USD', address = '' } = query; 16 | const { costUsd } = await ShippingGateway.getShippingCost(JSON.parse(itemList as string) as CartItem[], 17 | JSON.parse(address as string) as Address); 18 | const cost = await CurrencyGateway.convert(costUsd!, currencyCode as string); 19 | 20 | return res.status(200).json(cost!); 21 | } 22 | 23 | default: { 24 | return res.status(405); 25 | } 26 | } 27 | }; 28 | 29 | export default InstrumentationMiddleware(handler); 30 | -------------------------------------------------------------------------------- /src/frontend/pages/cart/index.tsx: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { faro } from '@grafana/faro-web-sdk'; 5 | import { NextPage } from 'next'; 6 | import { useEffect } from 'react'; 7 | import Footer from '../../components/Footer'; 8 | import Layout from '../../components/Layout'; 9 | import Recommendations from '../../components/Recommendations'; 10 | import * as S from '../../styles/Cart.styled'; 11 | import CartDetail from '../../components/Cart/CartDetail'; 12 | import EmptyCart from '../../components/Cart/EmptyCart'; 13 | import { useCart } from '../../providers/Cart.provider'; 14 | import AdProvider from '../../providers/Ad.provider'; 15 | 16 | const Cart: NextPage = () => { 17 | const { 18 | cart: { items }, 19 | } = useCart(); 20 | 21 | useEffect(() => { 22 | faro.api?.pushEvent('page', { 23 | name: 'Cart', 24 | }); 25 | }); 26 | 27 | return ( 28 | productId)} 30 | contextKeys={[...new Set(items.flatMap(({ product }) => product.categories))]} 31 | > 32 | 33 | 34 | {(!!items.length && ) || } 35 | 36 | 37 |