├── resultstoresearch ├── server │ ├── resultstoresearch │ │ ├── __init__.py │ │ ├── auth │ │ │ ├── __init__.py │ │ │ └── auth_interceptor.py │ │ ├── bigstore │ │ │ ├── __init__.py │ │ │ └── bigstore_client.py │ │ ├── mock │ │ │ ├── __init__.py │ │ │ ├── mock_proxy.py │ │ │ └── mock_responses.py │ │ ├── credentials │ │ │ └── __init__.py │ │ ├── firestore │ │ │ ├── __init__.py │ │ │ └── firestore_client.py │ │ ├── resultstoresearchapi │ │ │ ├── __init__.py │ │ │ ├── action_pb2_grpc.py │ │ │ ├── common_pb2_grpc.py │ │ │ ├── file_pb2_grpc.py │ │ │ ├── target_pb2_grpc.py │ │ │ ├── coverage_pb2_grpc.py │ │ │ ├── duration_pb2_grpc.py │ │ │ ├── invocation_pb2_grpc.py │ │ │ ├── test_suite_pb2_grpc.py │ │ │ ├── timestamp_pb2_grpc.py │ │ │ ├── wrappers_pb2_grpc.py │ │ │ ├── coverage_summary_pb2_grpc.py │ │ │ ├── file_processing_error_pb2_grpc.py │ │ │ ├── duration_pb2.py │ │ │ └── timestamp_pb2.py │ │ ├── resultstoreapi │ │ │ ├── cloud │ │ │ │ ├── devtools │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── resultstore │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ ├── v2 │ │ │ │ │ │ │ ├── gapic │ │ │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ │ │ ├── transports │ │ │ │ │ │ │ │ │ └── __init__.py │ │ │ │ │ │ │ │ ├── result_store_file_download_client_config.py │ │ │ │ │ │ │ │ └── result_store_download_client_config.py │ │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ │ └── types.py │ │ │ │ │ │ └── v2.py │ │ │ │ │ └── resultstore_v2 │ │ │ │ │ │ └── proto │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ ├── action_pb2_grpc.py │ │ │ │ │ │ ├── common_pb2_grpc.py │ │ │ │ │ │ ├── file_pb2_grpc.py │ │ │ │ │ │ ├── target_pb2_grpc.py │ │ │ │ │ │ ├── coverage_pb2_grpc.py │ │ │ │ │ │ ├── file_set_pb2_grpc.py │ │ │ │ │ │ ├── invocation_pb2_grpc.py │ │ │ │ │ │ ├── test_suite_pb2_grpc.py │ │ │ │ │ │ ├── configuration_pb2_grpc.py │ │ │ │ │ │ ├── configured_target_pb2_grpc.py │ │ │ │ │ │ ├── coverage_summary_pb2_grpc.py │ │ │ │ │ │ ├── download_metadata_pb2_grpc.py │ │ │ │ │ │ ├── upload_metadata_pb2_grpc.py │ │ │ │ │ │ ├── file_processing_error_pb2_grpc.py │ │ │ │ │ │ ├── resultstore_file_download_pb2_grpc.py │ │ │ │ │ │ ├── download_metadata_pb2.py │ │ │ │ │ │ └── upload_metadata_pb2.py │ │ │ │ └── __init__.py │ │ │ └── __init__.py │ │ ├── mock_server.py │ │ └── server.py │ ├── requirements.txt │ ├── Dockerfile.prod │ ├── Dockerfile.test │ └── setup.py ├── client │ ├── app.yaml │ ├── src │ │ ├── react-app-env.d.ts │ │ ├── setupTests.js │ │ ├── contexts │ │ │ └── AuthContext.ts │ │ ├── index.css │ │ ├── Pages │ │ │ ├── HomePage │ │ │ │ └── index.tsx │ │ │ ├── FlakyTestPage │ │ │ │ └── index.tsx │ │ │ └── FilePage │ │ │ │ └── index.tsx │ │ ├── index.js │ │ ├── config │ │ │ └── ConfigLoader.ts │ │ ├── App.css │ │ ├── components │ │ │ ├── ErrorText │ │ │ │ └── index.tsx │ │ │ ├── InvocationTable │ │ │ │ ├── types.ts │ │ │ │ ├── FileButton │ │ │ │ │ └── index.tsx │ │ │ │ └── utils.ts │ │ │ ├── BackButton │ │ │ │ └── index.tsx │ │ │ ├── InfiniteTable │ │ │ │ ├── BaseTable │ │ │ │ │ └── LoadingRow │ │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ ├── TestResults │ │ │ │ ├── TestCard │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ ├── SearchWrapper │ │ │ │ ├── SearchBar │ │ │ │ │ ├── SearchTooltip │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── FlakyTestButton │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── SearchButton │ │ │ │ │ │ └── index.tsx │ │ │ │ └── ToolSelect │ │ │ │ │ └── index.tsx │ │ │ ├── FlakyTestTable │ │ │ │ └── styles.ts │ │ │ ├── GoogleButton │ │ │ │ └── index.tsx │ │ │ └── StatusIcon │ │ │ │ └── index.tsx │ │ ├── api │ │ │ ├── duration_pb.d.ts │ │ │ ├── timestamp_pb.d.ts │ │ │ ├── file_processing_error_pb.d.ts │ │ │ ├── file_pb.d.ts │ │ │ └── coverage_summary_pb.d.ts │ │ ├── App.tsx │ │ └── utils │ │ │ └── index.ts │ ├── .prettierrc │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ └── index.html │ ├── envoy │ │ ├── Dockerfile │ │ └── envoy.yaml │ ├── typedoc.json │ ├── nginx.conf │ ├── .gitignore │ ├── tsconfig.json │ ├── Dockerfile │ ├── package.json │ └── README.md ├── e2e │ ├── cypress.json │ ├── cypress │ │ ├── videos │ │ │ └── app.spec.ts.mp4 │ │ ├── tsconfig.json │ │ └── integration │ │ │ ├── filePage.spec.ts │ │ │ └── app.spec.ts │ ├── package.json │ └── Dockerfile ├── resultstoresearch │ └── v1 │ │ ├── duration.proto │ │ ├── genprotos.sh │ │ ├── timestamp.proto │ │ ├── format.py │ │ ├── file_processing_error.proto │ │ ├── coverage_summary.proto │ │ ├── wrappers.proto │ │ ├── file.proto │ │ ├── coverage.proto │ │ └── target.proto └── ci │ ├── cloudbuild.prod.yaml │ ├── docker-compose.cypress.yaml │ ├── cloudbuild.test.yaml │ ├── example.env │ ├── docker-compose.test.yaml │ └── docker-compose.prod.yaml ├── resultstoreui ├── resultstoreapi │ ├── cloud │ │ ├── devtools │ │ │ ├── __init__.py │ │ │ ├── resultstore │ │ │ │ ├── __init__.py │ │ │ │ ├── v2 │ │ │ │ │ ├── gapic │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ ├── transports │ │ │ │ │ │ │ └── __init__.py │ │ │ │ │ │ ├── result_store_file_download_client_config.py │ │ │ │ │ │ └── result_store_download_client_config.py │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── types.py │ │ │ │ └── v2.py │ │ │ └── resultstore_v2 │ │ │ │ └── proto │ │ │ │ ├── __init__.py │ │ │ │ ├── action_pb2_grpc.py │ │ │ │ ├── common_pb2_grpc.py │ │ │ │ ├── file_pb2_grpc.py │ │ │ │ ├── target_pb2_grpc.py │ │ │ │ ├── coverage_pb2_grpc.py │ │ │ │ ├── file_set_pb2_grpc.py │ │ │ │ ├── invocation_pb2_grpc.py │ │ │ │ ├── test_suite_pb2_grpc.py │ │ │ │ ├── configuration_pb2_grpc.py │ │ │ │ ├── coverage_summary_pb2_grpc.py │ │ │ │ ├── upload_metadata_pb2_grpc.py │ │ │ │ ├── configured_target_pb2_grpc.py │ │ │ │ ├── download_metadata_pb2_grpc.py │ │ │ │ ├── file_processing_error_pb2_grpc.py │ │ │ │ ├── resultstore_file_download_pb2_grpc.py │ │ │ │ ├── download_metadata_pb2.py │ │ │ │ └── upload_metadata_pb2.py │ │ └── __init__.py │ └── __init__.py ├── requirements.txt ├── resultstore-v2-py.tar.gz ├── BUILD.bazel ├── WORKSPACE ├── credentials.py ├── bigstore_client.py ├── resultstoreui.py ├── resultstore_test_utils.py └── README.md ├── startup.sh ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE.md └── workflows │ └── docs-deploy.yml ├── docs ├── contributing.md └── code-of-conduct.md ├── .gitignore ├── README.md └── gRPC-build └── BUILD.bazel /resultstoresearch/server/resultstoresearch/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/devtools/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/auth/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/bigstore/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/mock/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resultstoresearch/client/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: custom 2 | env: flex 3 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/credentials/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/firestore/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/devtools/resultstore/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoresearchapi/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resultstoreui/requirements.txt: -------------------------------------------------------------------------------- 1 | grpcio 2 | protobuf 3 | google-api-core 4 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/devtools/resultstore/v2/gapic/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/devtools/resultstore_v2/proto/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/devtools/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/devtools/resultstore/v2/gapic/transports/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /resultstoresearch/e2e/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:3000" 3 | } 4 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/devtools/resultstore/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/devtools/resultstore/v2/gapic/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/devtools/resultstore_v2/proto/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/devtools/resultstore/v2/gapic/transports/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resultstoresearch/client/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "tabWidth": 4, 4 | "singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /resultstoresearch/client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /resultstoreui/resultstore-v2-py.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/resultstoreui/HEAD/resultstoreui/resultstore-v2-py.tar.gz -------------------------------------------------------------------------------- /resultstoresearch/client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/resultstoreui/HEAD/resultstoresearch/client/public/favicon.ico -------------------------------------------------------------------------------- /resultstoresearch/e2e/cypress/videos/app.spec.ts.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/resultstoreui/HEAD/resultstoresearch/e2e/cypress/videos/app.spec.ts.mp4 -------------------------------------------------------------------------------- /startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | eval $(egrep -v '^#' ./resultstoresearch/ci/.env | xargs) docker-compose -f ./resultstoresearch/ci/docker-compose.prod.yaml up --build 3 | -------------------------------------------------------------------------------- /resultstoresearch/client/envoy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM envoyproxy/envoy:v1.14.1 2 | COPY ./envoy.yaml /etc/envoy/envoy.yaml 3 | CMD /usr/local/bin/envoy -c /etc/envoy/envoy.yaml 4 | -------------------------------------------------------------------------------- /resultstoresearch/client/typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": ["./src"], 3 | "out": "./docs", 4 | "exclude": "./src/**/*+(.spec.ts|.stories.tsx|.test.ts)" 5 | } 6 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/devtools/resultstore_v2/proto/action_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/devtools/resultstore_v2/proto/common_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/devtools/resultstore_v2/proto/file_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/devtools/resultstore_v2/proto/target_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/devtools/resultstore_v2/proto/coverage_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/devtools/resultstore_v2/proto/file_set_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/devtools/resultstore_v2/proto/invocation_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/devtools/resultstore_v2/proto/test_suite_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/devtools/resultstore_v2/proto/configuration_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/devtools/resultstore_v2/proto/coverage_summary_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/devtools/resultstore_v2/proto/upload_metadata_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/devtools/resultstore_v2/proto/configured_target_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/devtools/resultstore_v2/proto/download_metadata_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/devtools/resultstore_v2/proto/file_processing_error_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /resultstoresearch/resultstoresearch/v1/duration.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package resultstoresearch.v1; 4 | 5 | message Duration { 6 | int64 seconds = 1; 7 | int32 nanos = 2; 8 | } 9 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/devtools/resultstore_v2/proto/action_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/devtools/resultstore_v2/proto/common_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/devtools/resultstore_v2/proto/file_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/devtools/resultstore_v2/proto/target_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/devtools/resultstore_v2/proto/coverage_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/devtools/resultstore_v2/proto/file_set_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/devtools/resultstore_v2/proto/invocation_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/devtools/resultstore_v2/proto/test_suite_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Fixes # 2 | 3 | > It's a good idea to open an issue first for discussion. 4 | 5 | - [ ] Tests pass 6 | - [ ] Appropriate changes to README are included in PR -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/devtools/resultstore_v2/proto/configuration_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/devtools/resultstore_v2/proto/configured_target_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/devtools/resultstore_v2/proto/coverage_summary_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/devtools/resultstore_v2/proto/download_metadata_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/devtools/resultstore_v2/proto/upload_metadata_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /resultstoresearch/e2e/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "e2e", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "devDependencies": { 6 | "cypress": "^4.8.0", 7 | "typescript": "^3.9.5" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/devtools/resultstore_v2/proto/file_processing_error_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Expected Behavior 2 | 3 | 4 | ## Actual Behavior 5 | 6 | 7 | ## Steps to Reproduce the Problem 8 | 9 | 1. 10 | 1. 11 | 1. 12 | 13 | ## Specifications 14 | 15 | - Version: 16 | - Platform: -------------------------------------------------------------------------------- /resultstoresearch/server/requirements.txt: -------------------------------------------------------------------------------- 1 | grpcio==1.29.0 2 | protobuf==3.12.2 3 | gcloud==0.18.3 4 | google-api-python-client==1.9.3 5 | firebase-admin==4.3.0 6 | gunicorn==20.0.4 7 | google-api-python-client==1.9.3 8 | google-cloud-storage==1.29.0 9 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoresearchapi/action_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoresearchapi/common_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoresearchapi/file_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoresearchapi/target_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoresearchapi/coverage_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoresearchapi/duration_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoresearchapi/invocation_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoresearchapi/test_suite_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoresearchapi/timestamp_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoresearchapi/wrappers_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoresearchapi/coverage_summary_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoresearchapi/file_processing_error_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | -------------------------------------------------------------------------------- /resultstoresearch/client/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | 4 | location / { 5 | root /usr/share/nginx/html; 6 | index index.html index.htm; 7 | try_files $uri $uri/ /index.html =404; 8 | } 9 | 10 | include /etc/nginx/extra-conf.d/*.conf; 11 | } 12 | -------------------------------------------------------------------------------- /resultstoresearch/ci/cloudbuild.prod.yaml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: node 3 | entrypoint: yarn 4 | args: ["install"] 5 | 6 | - name: "docker/compose:1.26.0" 7 | args: ["-f", "resultstoresearch/ci/docker-compose.prod.yaml", "up", "-d"] 8 | env: 9 | - "PROJECT_ID=$PROJECT_ID" 10 | -------------------------------------------------------------------------------- /resultstoresearch/e2e/cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "baseUrl": "../node_modules", 5 | "target": "es5", 6 | "lib": ["es5", "dom"], 7 | "types": ["cypress"] 8 | }, 9 | "include": ["**/*.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /resultstoresearch/server/Dockerfile.prod: -------------------------------------------------------------------------------- 1 | FROM python:3 2 | 3 | ENV INSTALL_HOME /usr/src/app 4 | ENV APP_HOME /usr/src/app/resultstoresearch 5 | WORKDIR $INSTALL_HOME 6 | COPY . ./ 7 | 8 | RUN pip install -r requirements.txt 9 | RUN pip install . 10 | 11 | WORKDIR $APP_HOME 12 | CMD ["python3", "-u", "server.py"] -------------------------------------------------------------------------------- /resultstoresearch/server/Dockerfile.test: -------------------------------------------------------------------------------- 1 | FROM python:3 2 | 3 | ENV INSTALL_HOME /usr/src/app 4 | ENV APP_HOME /usr/src/app/resultstoresearch 5 | WORKDIR $INSTALL_HOME 6 | COPY . ./ 7 | 8 | RUN pip install -r requirements.txt 9 | RUN pip install . 10 | 11 | WORKDIR $APP_HOME 12 | CMD ["python3", "-u", "mock_server.py"] -------------------------------------------------------------------------------- /resultstoresearch/e2e/cypress/integration/filePage.spec.ts: -------------------------------------------------------------------------------- 1 | context("ResultStoreSearch FilePage", () => { 2 | beforeEach(() => { 3 | cy.visit( 4 | "/file?prefix=51be7217-9798-4448-adf8-1e4428c71e9e&fileName=test-file" 5 | ); 6 | }); 7 | 8 | it("Should render a file", () => { 9 | cy.get("body").contains("Multi Dimensional Table (Plx custom chart chart)"); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/contexts/AuthContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | export type TokenId = string; 3 | 4 | export type AuthState = { 5 | tokenId: TokenId; 6 | setTokenId: (tokenId: TokenId) => void; 7 | }; 8 | 9 | const defaultSetTokenId = (tokenId: TokenId) => {}; 10 | 11 | export const AuthContext = createContext({ 12 | tokenId: '', 13 | setTokenId: defaultSetTokenId, 14 | }); 15 | -------------------------------------------------------------------------------- /resultstoresearch/ci/docker-compose.cypress.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | e2e: 4 | image: cypress 5 | build: ../e2e 6 | container_name: cypress 7 | network_mode: 'host' 8 | environment: 9 | - CYPRESS_baseUrl=http://localhost:3000/ 10 | - CYPRESS_browser=chrome 11 | command: npx cypress run 12 | volumes: 13 | - ../e2e/cypress:/app/cypress 14 | - ../e2e/cypress.json:/app/cypress.json 15 | -------------------------------------------------------------------------------- /resultstoresearch/client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /resultstoresearch/server/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup(name='resultstoresearch', 4 | version='0.1', 5 | description='Server for resultstoresearch', 6 | url='https://github.com/google/resultstoreui', 7 | author='Craig Lewis', 8 | author_email='lewiscraig@google.com', 9 | license='MIT', 10 | packages=find_packages(include=['resultstoresearch.*']), 11 | zip_safe=False) 12 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/Pages/HomePage/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import SearchWrapper from '../../components/SearchWrapper'; 4 | 5 | const AppContainer = styled.div` 6 | height: 98vh; 7 | width: 98vw; 8 | margin: 0 auto 0 auto; 9 | `; 10 | 11 | const Home = () => { 12 | return ( 13 | 14 | 15 | 16 | ); 17 | }; 18 | 19 | export default Home; 20 | -------------------------------------------------------------------------------- /resultstoresearch/ci/cloudbuild.test.yaml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: node 3 | entrypoint: yarn 4 | args: ["install"] 5 | 6 | - name: "docker/compose:1.26.0" 7 | args: ["-f", "resultstoresearch/ci/docker-compose.test.yaml", "up", "-d"] 8 | env: 9 | - "PROJECT_ID=$PROJECT_ID" 10 | - "CLIENT_ID=$_CLIENT_ID" 11 | - "ENVOY_ADDRESS=$_ENVOY_ADDRESS" 12 | - "PROJECT_ID=$_PROJECT_ID" 13 | 14 | - name: "docker/compose:1.26.0" 15 | args: ["-f", "resultstoresearch/ci/docker-compose.cypress.yaml", "up"] 16 | -------------------------------------------------------------------------------- /resultstoresearch/resultstoresearch/v1/genprotos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | INPUT_DIR=./ 3 | SERVER_OUTPUT_DIR=../../server/resultstoresearchapi 4 | CLIENT_OUTPUT_DIR=../../client/src/api/ 5 | 6 | protoc -I=$INPUT_DIR *.proto \ 7 | --js_out=import_style=commonjs,binary:$CLIENT_OUTPUT_DIR \ 8 | --grpc-web_out=import_style=typescript,mode=grpcwebtext:$CLIENT_OUTPUT_DIR 9 | 10 | python -m grpc_tools.protoc -I=$INPUT_DIR *.proto \ 11 | --python_out=$SERVER_OUTPUT_DIR \ 12 | --grpc_python_out=$SERVER_OUTPUT_DIR 13 | 14 | python format.py 15 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/bigstore/bigstore_client.py: -------------------------------------------------------------------------------- 1 | from google.cloud import storage 2 | from google import auth 3 | 4 | 5 | class BigstoreClient(): 6 | def __init__(self, creds): 7 | self.client = storage.Client( 8 | credentials=auth.default(scopes=creds.get_scopes())[0], 9 | project=creds.get_project_id()) 10 | self.bucket = self.client.get_bucket(creds.get_bucket_name()) 11 | 12 | def get_file_blob(self, file_name): 13 | blob = self.bucket.blob(file_name) 14 | return blob.download_as_string() 15 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want your app to work offline and load faster, you can change 15 | // unregister() to register() below. Note this comes with some pitfalls. 16 | // Learn more about service workers: https://bit.ly/CRA-PWA 17 | serviceWorker.unregister(); 18 | -------------------------------------------------------------------------------- /resultstoresearch/resultstoresearch/v1/timestamp.proto: -------------------------------------------------------------------------------- 1 | 2 | syntax = "proto3"; 3 | 4 | package resultstoresearch.v1; 5 | 6 | message Timestamp { 7 | // Represents seconds of UTC time since Unix epoch 8 | // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to 9 | // 9999-12-31T23:59:59Z inclusive. 10 | int64 seconds = 1; 11 | 12 | // Non-negative fractions of a second at nanosecond resolution. Negative 13 | // second values with fractions must still have non-negative nanos values 14 | // that count forward in time. Must be from 0 to 999,999,999 15 | // inclusive. 16 | int32 nanos = 2; 17 | } 18 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/config/ConfigLoader.ts: -------------------------------------------------------------------------------- 1 | interface DefaultConfig { 2 | destinationAddress: string; 3 | projectId: string; 4 | clientId: string; 5 | } 6 | 7 | const environmentConfig: DefaultConfig = { 8 | destinationAddress: 9 | process.env.REACT_APP_ENVOY_ADDRESS || 'http://localhost:8090', 10 | clientId: 11 | process.env.REACT_APP_CLIENT_ID || 12 | '835513274128-siubdukq9bv1rjq6fv3vvglf4eukte7m.apps.googleusercontent.com', 13 | projectId: 14 | process.env.REACT_APP_PROJECT_ID || 'google.com:gchips-productivity', 15 | }; 16 | 17 | export default environmentConfig; 18 | -------------------------------------------------------------------------------- /resultstoresearch/client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": false, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "react-jsx", 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /resultstoresearch/e2e/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM cypress/base:12 2 | WORKDIR /app 3 | 4 | # dependencies will be installed only if the package files change 5 | COPY package.json . 6 | COPY yarn.lock . 7 | 8 | # by setting CI environment variable we switch the Cypress install messages 9 | # to small "started / finished" and avoid 1000s of lines of progress messages 10 | # https://github.com/cypress-io/cypress/issues/1243 11 | ENV CI=1 12 | RUN yarn install --frozen-lockfile 13 | # verify that Cypress has been installed correctly. 14 | # running this command separately from "cypress run" will also cache its result 15 | # to avoid verifying again when running the tests 16 | RUN npx cypress verify 17 | -------------------------------------------------------------------------------- /resultstoresearch/client/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node as build-deps 2 | WORKDIR /usr/src/app 3 | COPY . ./ 4 | 5 | ARG REACT_APP_CLIENT_ID 6 | ENV REACT_APP_CLIENT_ID=$REACT_APP_CLIENT_ID 7 | 8 | ARG REACT_APP_PROJECT_ID 9 | ENV REACT_APP_PROJECT_ID=$REACT_APP_PROJECT_ID 10 | 11 | ARG REACT_APP_ENVOY_ADDRESS 12 | ENV REACT_APP_ENVOY_ADDRESS=$REACT_APP_ENVOY_ADDRESS 13 | 14 | RUN yarn 15 | RUN yarn build 16 | EXPOSE 80 17 | EXPOSE 3000 18 | 19 | FROM nginx:alpine 20 | COPY --from=build-deps /usr/src/app/build /usr/share/nginx/html 21 | COPY --from=build-deps /usr/src/app/nginx.conf /etc/nginx/conf.d/default.conf 22 | CMD ["nginx", "-g", "daemon off;"] 23 | 24 | # For Local Development 25 | # CMD ["yarn", "start"] 26 | -------------------------------------------------------------------------------- /resultstoresearch/ci/example.env: -------------------------------------------------------------------------------- 1 | # Template for server.env 2 | # Local service account json location 3 | # Instructions: https://cloud.google.com/iam/docs/creating-managing-service-account-keys 4 | SERVICE_ACCOUNT= 5 | 6 | # Google oauth 2.0 client id 7 | # Instructions: https://developers.google.com/identity/sign-in/web/sign-in#create_authorization_credentials 8 | CLIENT_ID= 9 | 10 | # GCP project id 11 | PROJECT_ID= 12 | 13 | # Public resultstore server url. Default: 'resultstore.googleapis.com' 14 | RESULT_STORE_API_ENDPOINT= 15 | 16 | # Envoy Port. Reflect changes in envoy.yaml. Default: 'http://localhost:8090' 17 | ENVOY_ADDRESS= 18 | 19 | # Name of google storage for files associated with invocations or targets 20 | BUCKET_NAME= -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/mock_server.py: -------------------------------------------------------------------------------- 1 | import grpc 2 | from concurrent import futures 3 | from resultstoresearchapi import (resultstore_download_pb2_grpc as 4 | resultstoresearch_download_pb2_grpc) 5 | from mock.mock_proxy import MockProxyServer 6 | 7 | PORT = '[::]:9090' 8 | 9 | 10 | def serve(): 11 | mock_proxy_server = MockProxyServer() 12 | server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) 13 | resultstoresearch_download_pb2_grpc.add_ResultStoreDownloadServicer_to_server( 14 | mock_proxy_server, server) 15 | server.add_insecure_port(PORT) 16 | server.start() 17 | server.wait_for_termination() 18 | 19 | 20 | if __name__ == '__main__': 21 | serve() 22 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /resultstoreui/BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | load("@grpc_deps//:requirements.bzl", "requirement") 4 | 5 | py_library( 6 | name = "resultstore_api", 7 | srcs = glob(["google/**/*.py"]), 8 | deps = [ 9 | requirement("grpcio"), 10 | requirement("protobuf"), 11 | requirement("google-api-core"), 12 | ] 13 | ) 14 | 15 | py_binary( 16 | name = "resultstoreui", 17 | srcs = glob(["*.py"]), 18 | python_version = 'PY3', 19 | deps = [ 20 | "resultstore_api", 21 | ], 22 | ) 23 | 24 | py_test( 25 | name = "test_resultstoreui", 26 | srcs = [ 27 | "resultstore_client_test.py" 28 | ], 29 | deps = [ 30 | "resultstoreui", 31 | "resultstore_api", 32 | ], 33 | main = "resultstore_client_test.py" 34 | ) 35 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/components/ErrorText/index.tsx: -------------------------------------------------------------------------------- 1 | // ErrorText Component 2 | /** 3 | * Component that displays error messages 4 | * @packageDocumentation 5 | */ 6 | import React from 'react'; 7 | import { Typography } from '@material-ui/core'; 8 | import styled from 'styled-components'; 9 | 10 | const Error = styled(Typography)` 11 | color: #ed3224; 12 | `; 13 | 14 | /** ErrorText Props */ 15 | interface Props { 16 | /** Button display text */ 17 | text: string; 18 | /** Button id */ 19 | id: string; 20 | } 21 | 22 | /** Component to display error text */ 23 | export const ErrorText: React.FC = ({ text, id }) => { 24 | return ( 25 | 26 | {text} 27 | 28 | ); 29 | }; 30 | 31 | export default ErrorText; 32 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/api/duration_pb.d.ts: -------------------------------------------------------------------------------- 1 | import * as jspb from "google-protobuf" 2 | 3 | export class Duration extends jspb.Message { 4 | getSeconds(): number; 5 | setSeconds(value: number): Duration; 6 | 7 | getNanos(): number; 8 | setNanos(value: number): Duration; 9 | 10 | serializeBinary(): Uint8Array; 11 | toObject(includeInstance?: boolean): Duration.AsObject; 12 | static toObject(includeInstance: boolean, msg: Duration): Duration.AsObject; 13 | static serializeBinaryToWriter(message: Duration, writer: jspb.BinaryWriter): void; 14 | static deserializeBinary(bytes: Uint8Array): Duration; 15 | static deserializeBinaryFromReader(message: Duration, reader: jspb.BinaryReader): Duration; 16 | } 17 | 18 | export namespace Duration { 19 | export type AsObject = { 20 | seconds: number, 21 | nanos: number, 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/api/timestamp_pb.d.ts: -------------------------------------------------------------------------------- 1 | import * as jspb from "google-protobuf" 2 | 3 | export class Timestamp extends jspb.Message { 4 | getSeconds(): number; 5 | setSeconds(value: number): Timestamp; 6 | 7 | getNanos(): number; 8 | setNanos(value: number): Timestamp; 9 | 10 | serializeBinary(): Uint8Array; 11 | toObject(includeInstance?: boolean): Timestamp.AsObject; 12 | static toObject(includeInstance: boolean, msg: Timestamp): Timestamp.AsObject; 13 | static serializeBinaryToWriter(message: Timestamp, writer: jspb.BinaryWriter): void; 14 | static deserializeBinary(bytes: Uint8Array): Timestamp; 15 | static deserializeBinaryFromReader(message: Timestamp, reader: jspb.BinaryReader): Timestamp; 16 | } 17 | 18 | export namespace Timestamp { 19 | export type AsObject = { 20 | seconds: number, 21 | nanos: number, 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright 2020 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 | # https://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 | try: 18 | import pkg_resources 19 | pkg_resources.declare_namespace(__name__) 20 | except ImportError: 21 | import pkgutil 22 | __path__ = pkgutil.extend_path(__path__, __name__) 23 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright 2020 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 | # https://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 | try: 18 | import pkg_resources 19 | pkg_resources.declare_namespace(__name__) 20 | except ImportError: 21 | import pkgutil 22 | __path__ = pkgutil.extend_path(__path__, __name__) 23 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright 2020 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 | # https://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 | try: 18 | import pkg_resources 19 | pkg_resources.declare_namespace(__name__) 20 | except ImportError: 21 | import pkgutil 22 | __path__ = pkgutil.extend_path(__path__, __name__) 23 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright 2020 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 | # https://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 | try: 18 | import pkg_resources 19 | pkg_resources.declare_namespace(__name__) 20 | except ImportError: 21 | import pkgutil 22 | __path__ = pkgutil.extend_path(__path__, __name__) 23 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'; 3 | import FilePage from './Pages/FilePage'; 4 | import Home from './Pages/HomePage'; 5 | import FlakyTest from './Pages/FlakyTestPage'; 6 | import { AuthContext, TokenId } from './contexts/AuthContext'; 7 | 8 | function App() { 9 | const [tokenId, setTokenId] = useState(''); 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | } 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/components/InvocationTable/types.ts: -------------------------------------------------------------------------------- 1 | import invocation_pb from '../../api/invocation_pb'; 2 | import { State as PageWrapperState } from '../SearchWrapper'; 3 | 4 | export interface Data { 5 | status: string; 6 | name: string; 7 | labels: string; 8 | date: string; 9 | duration: string; 10 | user: string; 11 | } 12 | 13 | export interface Column { 14 | id: 'status' | 'name' | 'labels' | 'date' | 'duration' | 'user'; 15 | label: string; 16 | minWidth?: number; 17 | align?: 'right'; 18 | } 19 | 20 | export interface InvocationTableProps { 21 | invocations: Array; 22 | pageToken: PageWrapperState['pageToken']; 23 | next: (newQuery: boolean, pageToken: string) => void; 24 | isNextPageLoading: boolean; 25 | tokenID: string; 26 | } 27 | 28 | export interface ModalState { 29 | isOpen: boolean; 30 | index: number; 31 | } 32 | 33 | export interface HoverState { 34 | isHovered: boolean; 35 | rowIndex: number; 36 | } 37 | -------------------------------------------------------------------------------- /resultstoreui/WORKSPACE: -------------------------------------------------------------------------------- 1 | workspace(name = "com_google_resultstore") 2 | 3 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 4 | 5 | # Python rules should go early in the dependencies list, otherwise a wrong 6 | # version of the library will be selected as a transitive dependency of gRPC. 7 | http_archive( 8 | name = "rules_python", 9 | url = "https://github.com/bazelbuild/rules_python/archive/748aa53d7701e71101dfd15d800e100f6ff8e5d1.zip", 10 | strip_prefix = "rules_python-748aa53d7701e71101dfd15d800e100f6ff8e5d1" 11 | ) 12 | 13 | load("@rules_python//python:repositories.bzl", "py_repositories") 14 | py_repositories() 15 | 16 | load("@rules_python//python:pip.bzl", "pip_repositories") 17 | pip_repositories() 18 | 19 | load("@rules_python//python:pip.bzl", "pip3_import") 20 | 21 | pip3_import( # or pip3_import 22 | name = "grpc_deps", 23 | requirements = "//:requirements.txt", 24 | ) 25 | 26 | # Load the central repo's install function from its `//:requirements.bzl` file, 27 | # and call it. 28 | load("@grpc_deps//:requirements.bzl", "pip_install") 29 | pip_install() 30 | -------------------------------------------------------------------------------- /.github/workflows/docs-deploy.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy Docs 2 | on: [push] 3 | jobs: 4 | build-and-deploy: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | with: 9 | working-directory: ./resultstoresearch/client 10 | - uses: actions/setup-node@master 11 | with: 12 | working-directory: ./resultstoresearch/client 13 | - name: Install Yarn 14 | working-directory: ./resultstoresearch/client 15 | run: | 16 | yarn install 17 | - name: Build Docs 18 | working-directory: ./resultstoresearch/client 19 | run: | 20 | yarn typedoc 21 | - name: Add No Jekyll 22 | working-directory: ./resultstoresearch/client/docs 23 | run: | 24 | touch .nojekyll 25 | - name: Deploy 🚀 26 | uses: JamesIves/github-pages-deploy-action@4.1.1 27 | with: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | BRANCH: gh-pages # The branch the action should deploy to. 30 | FOLDER: resultstoresearch/client/docs # The folder the action should deploy. 31 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/components/BackButton/index.tsx: -------------------------------------------------------------------------------- 1 | // BackButton Component 2 | /** 3 | * Component that allows users to navigte to previous page on click 4 | * @packageDocumentation 5 | */ 6 | import React from 'react'; 7 | import styled from 'styled-components'; 8 | import ArrowBackIcon from '@material-ui/icons/ArrowBack'; 9 | import Button from '@material-ui/core/Button'; 10 | 11 | const ButtonContainer = styled(Button)` 12 | height: 55px !important; 13 | width: 55px !important; 14 | margin-right: 8px !important; 15 | margin-top: auto !important; 16 | margin-bottom: auto !important; 17 | text-transform: none !important; 18 | font-size: 17px !important; 19 | `; 20 | 21 | /** BackButton Props */ 22 | interface Props { 23 | /** Callback fired on button click */ 24 | onClick: () => void; 25 | } 26 | 27 | /** BackButton Component */ 28 | export const BackButton: React.FC = ({ onClick }) => { 29 | return ( 30 | 31 | 32 | 33 | ); 34 | }; 35 | 36 | export default BackButton; 37 | -------------------------------------------------------------------------------- /resultstoresearch/resultstoresearch/v1/format.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import re 3 | 4 | CLIENT_OUTPUT_DIR = '../../client/src/api/*.js' 5 | SERVER_OUTPUT_DIR = '../../server/resultstoresearchapi/*.py' 6 | 7 | client_search_phrase = 'GENERATED CODE -- DO NOT EDIT!' 8 | client_replace_phrase = 'GENERATED CODE -- DO NOT EDIT!\n/* tslint:disable */\n/* eslint-disable */' 9 | 10 | server_search_phrase = '^import .*_pb2 as .*_pb2' 11 | server_replace_phrase = 'import resultstoresearchapi.' 12 | 13 | for filepath in glob.iglob(CLIENT_OUTPUT_DIR, recursive=False): 14 | with open(filepath) as file: 15 | s = file.read() 16 | s = s.replace(client_search_phrase, client_replace_phrase) 17 | with open(filepath, "w") as file: 18 | file.write(s) 19 | 20 | for filepath in glob.iglob(SERVER_OUTPUT_DIR, recursive=False): 21 | with open(filepath) as file: 22 | resp = '' 23 | for line in file: 24 | exists = re.search(server_search_phrase, line) 25 | if exists: 26 | line = server_replace_phrase + line[7:] 27 | resp += line 28 | with open(filepath, "w") as file: 29 | file.write(resp) 30 | -------------------------------------------------------------------------------- /resultstoresearch/ci/docker-compose.test.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | envoy: 4 | build: 5 | context: ../client/envoy/ 6 | network: host 7 | image: gcr.io/google.com/gchips-productivity/resultstoresearch-envoy-test 8 | expose: 9 | - "8090" 10 | - "9901" 11 | ports: 12 | - "8090:8090" 13 | - "9901:9901" 14 | 15 | client: 16 | build: 17 | context: ../client/ 18 | args: 19 | REACT_APP_PROJECT_ID: ${PROJECT_ID} 20 | REACT_APP_CLIENT_ID: ${CLIENT_ID} 21 | REACT_APP_ENVOY_ADDRESS: ${ENVOY_ADDRESS} 22 | image: gcr.io/google.com/gchips-productivity/resultstoresearch-client-test 23 | volumes: 24 | - ../client/:/usr/src/app 25 | - /usr/src/app/node_modules 26 | stdin_open: true 27 | ports: 28 | - "3000:80" 29 | expose: 30 | - "80" 31 | depends_on: 32 | - server 33 | - envoy 34 | 35 | server: 36 | build: 37 | context: ../server/ 38 | dockerfile: Dockerfile.test 39 | image: gcr.io/google.com/gchips-productivity/resultstoresearch-mock-server 40 | volumes: 41 | - ../server/:/usr/src/app 42 | ports: 43 | - "9090:9090" 44 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/devtools/resultstore/v2/gapic/result_store_file_download_client_config.py: -------------------------------------------------------------------------------- 1 | config = { 2 | "interfaces": { 3 | "google.devtools.resultstore.v2.ResultStoreFileDownload": { 4 | "retry_codes": { 5 | "idempotent": [ 6 | "DEADLINE_EXCEEDED", 7 | "UNAVAILABLE" 8 | ], 9 | "non_idempotent": [] 10 | }, 11 | "retry_params": { 12 | "default": { 13 | "initial_retry_delay_millis": 100, 14 | "retry_delay_multiplier": 1.3, 15 | "max_retry_delay_millis": 60000, 16 | "initial_rpc_timeout_millis": 20000, 17 | "rpc_timeout_multiplier": 1.0, 18 | "max_rpc_timeout_millis": 20000, 19 | "total_timeout_millis": 600000 20 | } 21 | }, 22 | "methods": { 23 | "GetFile": { 24 | "timeout_millis": 60000, 25 | "retry_codes_name": "idempotent", 26 | "retry_params_name": "default" 27 | }, 28 | "GetFileTail": { 29 | "timeout_millis": 60000, 30 | "retry_codes_name": "idempotent", 31 | "retry_params_name": "default" 32 | } 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/components/InvocationTable/FileButton/index.tsx: -------------------------------------------------------------------------------- 1 | // FileButton Component 2 | /** 3 | * Button that opens the file modal 4 | * @packageDocumentation 5 | */ 6 | import React from 'react'; 7 | import styled from 'styled-components'; 8 | import Button from '@material-ui/core/Button'; 9 | 10 | /** FileButton Props */ 11 | interface Props { 12 | /** Visible if true else hidden */ 13 | isVisible: boolean; 14 | /** Callback fired on button click */ 15 | onClick: (e: React.MouseEvent) => void; 16 | /** FileButton id */ 17 | id: string; 18 | } 19 | 20 | const FileButtonContainer = styled(Button)` 21 | margin-left: auto; 22 | margin-right: auto; 23 | visibility: ${({ isVisible }) => (isVisible ? 'visible' : 'hidden')}; 24 | `; 25 | 26 | /** FileButton Props */ 27 | export const FileButton: React.FC = ({ onClick, isVisible, id }) => { 28 | return ( 29 | 35 | Files 36 | 37 | ); 38 | }; 39 | 40 | export default FileButton; 41 | -------------------------------------------------------------------------------- /docs/contributing.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution; 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows [Google's Open Source Community 28 | Guidelines](https://opensource.google/conduct/). 29 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/components/InfiniteTable/BaseTable/LoadingRow/index.tsx: -------------------------------------------------------------------------------- 1 | // LoadingRow Component 2 | /** 3 | * Component that adds a place holder row with a loading spinner 4 | * while data is currently being loaded in a table 5 | * @packageDocumentation 6 | */ 7 | import React from 'react'; 8 | import styled from 'styled-components'; 9 | import CircularProgress from '@material-ui/core/CircularProgress'; 10 | 11 | const TableSpinner = styled(CircularProgress)` 12 | color: #6e6e6e !important; 13 | `; 14 | 15 | const SpinnerContainer = styled.div` 16 | margin-top: 5px; 17 | width: ${({ width }) => width}px; 18 | display: flex; 19 | align-items: center; 20 | justify-content: center; 21 | `; 22 | 23 | /** LoadingRow Props */ 24 | interface Props { 25 | /** width of the row */ 26 | width: number; 27 | /** Size of the spinner svg */ 28 | size?: number; 29 | } 30 | 31 | /** LoadingRow Component */ 32 | export const LoadingRow: React.FC = ({ width, size = 25 }) => { 33 | return ( 34 | 35 | 36 | 37 | ); 38 | }; 39 | 40 | export default LoadingRow; 41 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/devtools/resultstore/v2/gapic/result_store_file_download_client_config.py: -------------------------------------------------------------------------------- 1 | config = { 2 | "interfaces": { 3 | "google.devtools.resultstore.v2.ResultStoreFileDownload": { 4 | "retry_codes": { 5 | "idempotent": [ 6 | "DEADLINE_EXCEEDED", 7 | "UNAVAILABLE" 8 | ], 9 | "non_idempotent": [] 10 | }, 11 | "retry_params": { 12 | "default": { 13 | "initial_retry_delay_millis": 100, 14 | "retry_delay_multiplier": 1.3, 15 | "max_retry_delay_millis": 60000, 16 | "initial_rpc_timeout_millis": 20000, 17 | "rpc_timeout_multiplier": 1.0, 18 | "max_rpc_timeout_millis": 20000, 19 | "total_timeout_millis": 600000 20 | } 21 | }, 22 | "methods": { 23 | "GetFile": { 24 | "timeout_millis": 60000, 25 | "retry_codes_name": "idempotent", 26 | "retry_params_name": "default" 27 | }, 28 | "GetFileTail": { 29 | "timeout_millis": 60000, 30 | "retry_codes_name": "idempotent", 31 | "retry_params_name": "default" 32 | } 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/devtools/resultstore/v2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright 2020 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 | # https://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 | 18 | from __future__ import absolute_import 19 | 20 | from google.cloud.devtools.resultstore.v2 import ResultStoreDownloadClient 21 | from google.cloud.devtools.resultstore.v2 import ResultStoreFileDownloadClient 22 | from google.cloud.devtools.resultstore.v2 import ResultStoreUploadClient 23 | from google.cloud.devtools.resultstore.v2 import enums 24 | from google.cloud.devtools.resultstore.v2 import types 25 | 26 | 27 | __all__ = ( 28 | 'enums', 29 | 'types', 30 | 'ResultStoreDownloadClient', 31 | 'ResultStoreFileDownloadClient', 32 | 'ResultStoreUploadClient', 33 | ) 34 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | import common_pb from '../api/common_pb'; 3 | 4 | /* 5 | Converts inputted string to sentence case 6 | 7 | Args: 8 | theString: Inputted string to be converted 9 | */ 10 | const toSentenceCase = (theString) => { 11 | const newString = theString.toLowerCase().replace( 12 | // eslint-disable-next-line 13 | /(^\s*\w|[\.\!\?]\s*\w)/g, 14 | (c) => { 15 | return c.toUpperCase(); 16 | } 17 | ); 18 | return newString; 19 | }; 20 | 21 | const getDate = (timing: common_pb.Timing | undefined) => { 22 | const startTiming = 23 | (timing && 24 | timing.getStartTime() && 25 | timing.getStartTime().getSeconds()) || 26 | 0; 27 | const startTime = moment.unix(startTiming); 28 | return startTime.format(`YYYY-MM-DD, hh:mm A`); 29 | }; 30 | 31 | const flakyTestHeaderDate = (timing: common_pb.Timing | undefined) => { 32 | const startTiming = 33 | (timing && 34 | timing.getStartTime() && 35 | timing.getStartTime().getSeconds()) || 36 | 0; 37 | const startTime = moment.unix(startTiming); 38 | return startTime.format(`MM/DD hh:mm`); 39 | }; 40 | 41 | export { toSentenceCase, getDate, flakyTestHeaderDate }; 42 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/devtools/resultstore/v2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright 2020 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 | # https://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 | 18 | from __future__ import absolute_import 19 | 20 | from google.cloud.devtools.resultstore.v2 import ResultStoreDownloadClient 21 | from google.cloud.devtools.resultstore.v2 import ResultStoreFileDownloadClient 22 | from google.cloud.devtools.resultstore.v2 import ResultStoreUploadClient 23 | from google.cloud.devtools.resultstore.v2 import enums 24 | from google.cloud.devtools.resultstore.v2 import types 25 | 26 | 27 | __all__ = ( 28 | 'enums', 29 | 'types', 30 | 'ResultStoreDownloadClient', 31 | 'ResultStoreFileDownloadClient', 32 | 'ResultStoreUploadClient', 33 | ) 34 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/components/TestResults/TestCard/index.tsx: -------------------------------------------------------------------------------- 1 | // TestCard Component 2 | /** 3 | * Card to display individual aggregate test results 4 | * @packageDocumentation 5 | */ 6 | import React from 'react'; 7 | import Card from '@material-ui/core/Card'; 8 | import styled from 'styled-components'; 9 | 10 | const CardContainer = styled(Card)` 11 | display: flex; 12 | flex: 1; 13 | margin: auto 5px auto 5px; 14 | height: 55px; 15 | width: 175px; 16 | align-items: center; 17 | justify-content: center; 18 | font-weight: bold; 19 | `; 20 | 21 | const TextContainer = styled.div<{ flex: number }>` 22 | display: flex; 23 | align-items: center; 24 | justify-content: center; 25 | text-align: left; 26 | font-weight: bold; 27 | margin: auto 15px auto 10px; 28 | `; 29 | 30 | const NumberContainer = styled.div` 31 | display: flex; 32 | align-items: center; 33 | justify-content: center; 34 | text-align: center; 35 | font-size: 19px; 36 | margin: auto 15px auto 0px; 37 | `; 38 | 39 | /** TestCard Props */ 40 | interface Props { 41 | /** Total number of tests */ 42 | numberTests: number; 43 | } 44 | 45 | /** TestCard Component */ 46 | const TestCard: React.FC = ({ children, numberTests }) => { 47 | return ( 48 | 49 | {children} 50 | {numberTests} 51 | 52 | ); 53 | }; 54 | 55 | export default TestCard; 56 | -------------------------------------------------------------------------------- /resultstoresearch/ci/docker-compose.prod.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | envoy: 4 | build: 5 | context: ../client/envoy/ 6 | network: host 7 | image: gcr.io/google.com/gchips-productivity/resultstoresearch-envoy 8 | expose: 9 | - "8090" 10 | - "9901" 11 | ports: 12 | - "8090:8090" 13 | - "9901:9901" 14 | 15 | client: 16 | build: 17 | context: ../client/ 18 | args: 19 | REACT_APP_PROJECT_ID: ${PROJECT_ID} 20 | REACT_APP_CLIENT_ID: ${CLIENT_ID} 21 | REACT_APP_ENVOY_ADDRESS: ${ENVOY_ADDRESS} 22 | image: gcr.io/google.com/gchips-productivity/resultstoresearch-client 23 | env_file: .env 24 | volumes: 25 | - ../client/:/usr/src/app 26 | - /usr/src/app/node_modules 27 | ports: 28 | - "3000:80" 29 | expose: 30 | - "3000" 31 | stdin_open: true 32 | depends_on: 33 | - server 34 | - envoy 35 | 36 | server: 37 | build: 38 | context: ../server/ 39 | dockerfile: Dockerfile.prod 40 | image: gcr.io/google.com/gchips-productivity/resultstoresearch-server 41 | env_file: .env 42 | environment: 43 | GOOGLE_APPLICATION_CREDENTIALS: /root/keys/keyfile.json 44 | CLIENT_ID: ${CLIENT_ID} 45 | PROJECT_ID: ${PROJECT_ID} 46 | RESULT_STORE_API_ENDPOINT: ${RESULT_STORE_API_ENDPOINT} 47 | BUCKET_NAME: ${BUCKET_NAME} 48 | volumes: 49 | - ../server/:/usr/src/app 50 | - ${SERVICE_ACCOUNT}:/root/keys/keyfile.json:ro 51 | ports: 52 | - "9090:9090" 53 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/firestore/firestore_client.py: -------------------------------------------------------------------------------- 1 | import firebase_admin 2 | import pytz 3 | from firebase_admin import (credentials, firestore) 4 | from datetime import datetime, timedelta 5 | 6 | VERIFICATION_TIMEOUT = timedelta(hours=24) 7 | 8 | 9 | class FireStoreClient(): 10 | """ Resultstore search's firestore client""" 11 | def __init__(self, project_id): 12 | """ 13 | Initialize the client 14 | 15 | Args: 16 | project_id (str): GCP project_id 17 | """ 18 | cred = credentials.ApplicationDefault() 19 | firebase_admin.initialize_app(cred, { 20 | 'projectId': project_id, 21 | }) 22 | 23 | self.db = firestore.client() 24 | 25 | def add_tool_tag(self, tool_tag): 26 | """ 27 | Add the provided tool_tags to firestores' resultstore_tools collection 28 | 29 | Args: 30 | tool_tag (str): Tool tag to be added 31 | """ 32 | col_ref = self.db.collection(u'resultstore_tools') 33 | col_ref.add({u'name': tool_tag}) 34 | 35 | def get_tools(self): 36 | """ 37 | Queries resultstore_tools collection from firestore and returns 38 | a list of tool names 39 | 40 | Returns 41 | (Seq[str]) list of tools 42 | """ 43 | tools_ref = self.db.collection(u'resultstore_tools') 44 | docs = tools_ref.stream() 45 | tools = [] 46 | for doc in docs: 47 | tools.append(doc.get('name')) 48 | return tools 49 | -------------------------------------------------------------------------------- /resultstoresearch/resultstoresearch/v1/file_processing_error.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package resultstoresearch.v1; 4 | 5 | // Stores errors reading or parsing a file during post-processing. 6 | message FileProcessingErrors { 7 | // The uid of the File being read or parsed. 8 | string file_uid = 1; 9 | 10 | // What went wrong. 11 | repeated FileProcessingError file_processing_errors = 3; 12 | } 13 | 14 | // Stores an error reading or parsing a file during post-processing. 15 | message FileProcessingError { 16 | // The type of error that occurred. 17 | FileProcessingErrorType type = 1; 18 | 19 | // Error message describing the problem. 20 | string message = 2; 21 | } 22 | 23 | // Errors in file post-processing are categorized using this enum. 24 | enum FileProcessingErrorType { 25 | // Type unspecified or not listed here. 26 | FILE_PROCESSING_ERROR_TYPE_UNSPECIFIED = 0; 27 | 28 | // A read error occurred trying to read the file. 29 | GENERIC_READ_ERROR = 1; 30 | 31 | // There was an error trying to parse the file. 32 | GENERIC_PARSE_ERROR = 2; 33 | 34 | // File is exceeds size limit. 35 | FILE_TOO_LARGE = 3; 36 | 37 | // The result of parsing the file exceeded size limit. 38 | OUTPUT_TOO_LARGE = 4; 39 | 40 | // Read access to the file was denied by file system. 41 | ACCESS_DENIED = 5; 42 | 43 | // Deadline exceeded trying to read the file. 44 | DEADLINE_EXCEEDED = 6; 45 | 46 | // File not found. 47 | NOT_FOUND = 7; 48 | 49 | // File is empty but was expected to have content. 50 | FILE_EMPTY = 8; 51 | } 52 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/server.py: -------------------------------------------------------------------------------- 1 | from concurrent import futures 2 | from resultstoreapi.cloud.devtools.resultstore_v2.proto import ( 3 | resultstore_download_pb2_grpc, ) 4 | from resultstoresearchapi import ( 5 | resultstore_download_pb2_grpc as resultstoresearch_download_pb2_grpc, 6 | resultstore_download_pb2 as resultstoresearch_download_pb2, 7 | ) 8 | from credentials.credentials import Credentials 9 | from resultstore_proxy_server import ProxyServer 10 | from firestore.firestore_client import FireStoreClient 11 | from bigstore.bigstore_client import BigstoreClient 12 | from auth.auth_interceptor import AuthInterceptor 13 | import logging 14 | import grpc 15 | import sys 16 | 17 | 18 | def serve(): 19 | creds = Credentials() 20 | fs = FireStoreClient(creds.get_project_id()) 21 | bs = BigstoreClient(creds) 22 | auth_interceptor = AuthInterceptor(grpc.StatusCode.UNAUTHENTICATED, 23 | 'Invalid Authorization', creds) 24 | server = grpc.server(futures.ThreadPoolExecutor(max_workers=10), 25 | interceptors=(auth_interceptor, )) 26 | with creds.create_secure_channel(creds.get_destination_sever()) as channel: 27 | proxy_server = ProxyServer(channel, fs, bs) 28 | resultstoresearch_download_pb2_grpc.add_ResultStoreDownloadServicer_to_server( 29 | proxy_server, server) 30 | server.add_insecure_port(creds.get_port()) 31 | server.start() 32 | server.wait_for_termination() 33 | 34 | 35 | if __name__ == '__main__': 36 | serve() 37 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/components/SearchWrapper/SearchBar/SearchTooltip/index.tsx: -------------------------------------------------------------------------------- 1 | // SearchTooltip Component 2 | /** 3 | * Tooltip on the search bar 4 | * @packageDocumentation 5 | */ 6 | import React from 'react'; 7 | import Tooltip from '@material-ui/core/Tooltip'; 8 | import HelpOutlineIcon from '@material-ui/icons/HelpOutline'; 9 | import Typography from '@material-ui/core/Typography'; 10 | 11 | /** SearchTooltip Component */ 12 | export const SearchTooltip: React.FC = () => { 13 | return ( 14 | 17 | {'Fields that support equals ("=") restrictions:'}
18 | {'name'}
19 | {'status_attributes.status'}
20 | {'workspace_info.hostname'}

21 | {'Fields that support contains (":") restrictions:'}
22 | {'invocation_attributes.users'}
23 | {'invocation_attributes.labels'}

24 | { 25 | 'Fields that support comparison ("<", "<=", ">", ">=") restrictions:' 26 | } 27 |
28 | {'timing.start_time'}

29 | {'Supported custom function global restrictions:'}
30 | {'propertyEquals("key", "value")'} 31 | 32 | } 33 | aria-label="SearchHelp" 34 | > 35 | 36 |
37 | ); 38 | }; 39 | 40 | export default SearchTooltip; 41 | -------------------------------------------------------------------------------- /resultstoresearch/resultstoresearch/v1/coverage_summary.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package resultstoresearch.v1; 4 | 5 | import "common.proto"; 6 | 7 | // Summary of line coverage 8 | message LineCoverageSummary { 9 | // Number of lines instrumented for coverage. 10 | int32 instrumented_line_count = 1; 11 | 12 | // Number of instrumented lines that were executed by the test. 13 | int32 executed_line_count = 2; 14 | } 15 | 16 | // Summary of branch coverage 17 | // A branch may be: 18 | // * not executed. Counted only in total. 19 | // * executed but not taken. Appears in total and executed. 20 | // * executed and taken. Appears in all three fields. 21 | message BranchCoverageSummary { 22 | // The number of branches present in the file. 23 | int32 total_branch_count = 1; 24 | 25 | // The number of branches executed out of the total branches present. 26 | // A branch is executed when its condition is evaluated. 27 | // This is <= total_branch_count as not all branches are executed. 28 | int32 executed_branch_count = 2; 29 | 30 | // The number of branches taken out of the total branches executed. 31 | // A branch is taken when its condition is satisfied. 32 | // This is <= executed_branch_count as not all executed branches are taken. 33 | int32 taken_branch_count = 3; 34 | } 35 | 36 | // Summary of coverage in each language 37 | message LanguageCoverageSummary { 38 | // This summary is for all files written in this programming language. 39 | Language language = 1; 40 | 41 | // Summary of lines covered vs instrumented. 42 | LineCoverageSummary line_summary = 2; 43 | 44 | // Summary of branch coverage. 45 | BranchCoverageSummary branch_summary = 3; 46 | } 47 | -------------------------------------------------------------------------------- /resultstoreui/credentials.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | from google import auth 3 | from google.auth.transport import grpc as google_auth_transport_grpc 4 | from google.auth.transport import requests as google_auth_transport_requests 5 | 6 | BIGSTORE_SCOPES = [ 7 | 'https://www.googleapis.com/auth/devstorage.write_only', 8 | ] 9 | 10 | RESULTSTORE_SCOPES = [ 11 | "https://www.googleapis.com/auth/cloud-source-tools", 12 | "https://www.googleapis.com/auth/cloud-platform" 13 | ] 14 | 15 | ALL_SCOPES = BIGSTORE_SCOPES + RESULTSTORE_SCOPES 16 | 17 | 18 | class Credentials(): 19 | """ Credentials container/helper for resultstoreui""" 20 | def __init__(self): 21 | """ 22 | Initialize Credentials 23 | """ 24 | self.channel = None 25 | self.scopes = ALL_SCOPES 26 | 27 | @contextlib.contextmanager 28 | def create_secure_channel(self, addr): 29 | """ 30 | Creates a secure channel using GOOGLE_APPLICATION_CREDENTIALS from the 31 | users path 32 | 33 | Args: 34 | target (str): The host and port of the service 35 | 36 | Returns: 37 | A gRPC channel 38 | """ 39 | credentials, _ = auth.default(scopes=self.scopes) 40 | request = google_auth_transport_requests.Request() 41 | channel = google_auth_transport_grpc.secure_authorized_channel( 42 | credentials, request, addr) 43 | self.channel = channel 44 | yield channel 45 | 46 | def get_active_channel(self): 47 | """Returns current active channel""" 48 | return self.channel 49 | 50 | def get_scopes(self): 51 | """Returns scopes""" 52 | return self.scopes 53 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/components/SearchWrapper/SearchBar/FlakyTestButton/index.tsx: -------------------------------------------------------------------------------- 1 | // FlakyTestButton Component 2 | /** 3 | * Button that initiates query and redirects to a flaky test page 4 | * @packageDocumentation 5 | */ 6 | import React from 'react'; 7 | import styled from 'styled-components'; 8 | import Button from '@material-ui/core/Button'; 9 | import DirectionsRunIcon from '@material-ui/icons/DirectionsRun'; 10 | import CircularProgress from '@material-ui/core/CircularProgress'; 11 | 12 | const ButtonContainer = styled(Button)` 13 | height: 56px; 14 | margin-right: 8px !important; 15 | margin-top: auto !important; 16 | margin-bottom: auto !important; 17 | text-transform: none !important; 18 | font-size: 17px !important; 19 | `; 20 | 21 | const TextContainer = styled.span` 22 | margin-left: 4px; 23 | `; 24 | 25 | const TableSpinner = styled(CircularProgress)` 26 | color: #6e6e6e !important; 27 | `; 28 | 29 | /** FlakyTestButton Props */ 30 | interface Props { 31 | /** Callback fired on click*/ 32 | onClick: () => void; 33 | /** Disabled if true */ 34 | disabled: boolean; 35 | /** Show spinner if true */ 36 | showSpinner: boolean; 37 | } 38 | 39 | /** FlakyTestButton Component */ 40 | export const FlakyTestButton: React.FC = ({ 41 | onClick, 42 | disabled, 43 | showSpinner, 44 | }) => { 45 | return ( 46 | 52 | {showSpinner && } 53 | {!showSpinner && } 54 | {'Flaky '} 55 | 56 | ); 57 | }; 58 | export default FlakyTestButton; 59 | -------------------------------------------------------------------------------- /resultstoresearch/client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 16 | 25 | ResultStoreSearch 26 | 27 | 28 | 29 |
30 | 40 | 41 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/components/TestResults/index.tsx: -------------------------------------------------------------------------------- 1 | // TestResults Component 2 | /** 3 | * Container to display aggregations of tests, passed tests and failed tests 4 | * @packageDocumentation 5 | */ 6 | import React from 'react'; 7 | import styled from 'styled-components'; 8 | import Typography from '@material-ui/core/Typography'; 9 | import TestCard from './TestCard'; 10 | import StatusIcon from '../StatusIcon'; 11 | 12 | const TestResultsContainer = styled.div` 13 | display: flex; 14 | margin: 0 auto 0 auto; 15 | `; 16 | 17 | const TextContainer = styled(Typography)` 18 | font-weight: bold !important; 19 | margin-right: 6px !important; 20 | font-size: 19px !important; 21 | `; 22 | 23 | /** TestResults Props*/ 24 | export interface Props { 25 | /** Total tests conducted */ 26 | totalTests: number; 27 | /** Total number of tests failed */ 28 | failedTests: number; 29 | /** Total number of tests passed */ 30 | passedTests: number; 31 | } 32 | 33 | /** TestResults Component */ 34 | export const TestResults: React.FC = ({ 35 | totalTests, 36 | failedTests, 37 | passedTests, 38 | }) => { 39 | return ( 40 | 41 | 42 | Total 43 | 44 | 45 | 46 | Passed 47 | 48 | 49 | 50 | Failed 51 | 52 | 53 | 54 | ); 55 | }; 56 | 57 | export default TestResults; 58 | -------------------------------------------------------------------------------- /resultstoresearch/resultstoresearch/v1/wrappers.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package resultstoresearch.v1; 4 | 5 | // Wrapper message for `double`. 6 | // 7 | // The JSON representation for `DoubleValue` is JSON number. 8 | message DoubleValue { 9 | // The double value. 10 | double value = 1; 11 | } 12 | 13 | // Wrapper message for `float`. 14 | // 15 | // The JSON representation for `FloatValue` is JSON number. 16 | message FloatValue { 17 | // The float value. 18 | float value = 1; 19 | } 20 | 21 | // Wrapper message for `int64`. 22 | // 23 | // The JSON representation for `Int64Value` is JSON string. 24 | message Int64Value { 25 | // The int64 value. 26 | int64 value = 1; 27 | } 28 | 29 | // Wrapper message for `uint64`. 30 | // 31 | // The JSON representation for `UInt64Value` is JSON string. 32 | message UInt64Value { 33 | // The uint64 value. 34 | uint64 value = 1; 35 | } 36 | 37 | // Wrapper message for `int32`. 38 | // 39 | // The JSON representation for `Int32Value` is JSON number. 40 | message Int32Value { 41 | // The int32 value. 42 | int32 value = 1; 43 | } 44 | 45 | // Wrapper message for `uint32`. 46 | // 47 | // The JSON representation for `UInt32Value` is JSON number. 48 | message UInt32Value { 49 | // The uint32 value. 50 | uint32 value = 1; 51 | } 52 | 53 | // Wrapper message for `bool`. 54 | // 55 | // The JSON representation for `BoolValue` is JSON `true` and `false`. 56 | message BoolValue { 57 | // The bool value. 58 | bool value = 1; 59 | } 60 | 61 | // Wrapper message for `string`. 62 | // 63 | // The JSON representation for `StringValue` is JSON string. 64 | message StringValue { 65 | // The string value. 66 | string value = 1; 67 | } 68 | 69 | // Wrapper message for `bytes`. 70 | // 71 | // The JSON representation for `BytesValue` is JSON string. 72 | message BytesValue { 73 | // The bytes value. 74 | bytes value = 1; 75 | } 76 | -------------------------------------------------------------------------------- /resultstoresearch/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "resultstore-search-client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^4.10.0", 7 | "@material-ui/icons": "^4.9.1", 8 | "@material-ui/lab": "^4.0.0-alpha.56", 9 | "@testing-library/jest-dom": "^4.2.4", 10 | "@testing-library/react": "^9.3.2", 11 | "@testing-library/user-event": "^7.1.2", 12 | "clsx": "^1.1.1", 13 | "google-protobuf": "^3.12.2", 14 | "grpc-web": "^1.2.0", 15 | "moment": "^2.26.0", 16 | "query-string": "^6.13.1", 17 | "react": "^16.13.1", 18 | "react-dom": "^16.13.1", 19 | "react-google-login": "^5.1.20", 20 | "react-router-dom": "^5.2.0", 21 | "react-scripts": "^4.0.3", 22 | "react-virtualized": "^9.21.2", 23 | "styled-components": "^5.1.1", 24 | "typescript": "^4.2.4" 25 | }, 26 | "scripts": { 27 | "start": "react-scripts start", 28 | "build": "react-scripts build", 29 | "eject": "react-scripts eject" 30 | }, 31 | "eslintConfig": { 32 | "extends": "react-app" 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.2%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | }, 46 | "devDependencies": { 47 | "@types/jest": "^26.0.4", 48 | "@types/node": "^14.0.20", 49 | "@types/query-string": "^6.3.0", 50 | "@types/react": "^16.9.41", 51 | "@types/react-dom": "^16.9.8", 52 | "@types/react-router-dom": "^5.1.5", 53 | "@types/react-virtualized": "^9.21.10", 54 | "env-cmd": "^10.1.0", 55 | "prettier": "^2.0.5", 56 | "typedoc": "^0.20.36" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/components/SearchWrapper/SearchBar/SearchButton/index.tsx: -------------------------------------------------------------------------------- 1 | // SearchButton Component 2 | /** 3 | * Button that initiates invocation search 4 | * @packageDocumentation 5 | */ 6 | import React from 'react'; 7 | import styled from 'styled-components'; 8 | import Button from '@material-ui/core/Button'; 9 | import CircularProgress from '@material-ui/core/CircularProgress'; 10 | import Search from '@material-ui/icons/Search'; 11 | 12 | const SearchButtonContainer = styled(Button)` 13 | height: 56px; 14 | margin-right: 8px !important; 15 | margin-top: auto !important; 16 | margin-bottom: auto !important; 17 | text-transform: none !important; 18 | font-size: 17px !important; 19 | `; 20 | 21 | const TableSpinner = styled(CircularProgress)` 22 | color: #6e6e6e !important; 23 | `; 24 | 25 | const SearchIcon = styled(Search)` 26 | color: #6e6e6e !important; 27 | font-size: 25px !important; 28 | `; 29 | 30 | const TextContainer = styled.span` 31 | margin-left: 4px; 32 | `; 33 | 34 | /** SearchButton Props */ 35 | interface Props { 36 | /** Show spinner if true */ 37 | showSpinner: boolean; 38 | /** Size of spinner svg */ 39 | size?: number; 40 | /** Callback fired on click */ 41 | onClick?: () => void; 42 | /** Disabled if true */ 43 | disabled?: boolean; 44 | } 45 | 46 | /** SearchButton Components */ 47 | export const SearchButton: React.FC = ({ 48 | showSpinner, 49 | size = 25, 50 | onClick, 51 | disabled, 52 | }) => { 53 | return ( 54 | 60 | {showSpinner && } 61 | {!showSpinner && } 62 | {'Search'} 63 | 64 | ); 65 | }; 66 | 67 | export default SearchButton; 68 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/components/InfiniteTable/index.tsx: -------------------------------------------------------------------------------- 1 | // InfiniteTable Component 2 | /** 3 | * Component that adds an infinite loader to the BaseTable Componenet 4 | * @packageDocumentation 5 | */ 6 | import React from 'react'; 7 | import { TableProps, InfiniteLoader } from 'react-virtualized'; 8 | import BaseTable, { SelfProps as BaseTableProps } from './BaseTable'; 9 | 10 | /** InfiniteTable Props */ 11 | type Props = BaseTableProps & TableProps; 12 | 13 | /** InfiniteTable Component */ 14 | export const InfiniteTable: React.FC = ({ 15 | isRowLoaded, 16 | loadMoreRows, 17 | columns, 18 | width, 19 | height, 20 | rowHeight, 21 | headerHeight, 22 | rowCount, 23 | rowGetter, 24 | rowClassName, 25 | onRowClick, 26 | headerClass, 27 | cellClass, 28 | gridClass, 29 | onRowMouseOver, 30 | onRowMouseOut, 31 | isNextPageLoading, 32 | rowRenderer, 33 | }) => { 34 | return ( 35 | 41 | {({ onRowsRendered, registerChild }) => ( 42 | 62 | )} 63 | 64 | ); 65 | }; 66 | 67 | export default InfiniteTable; 68 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/Pages/FlakyTestPage/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import styled from 'styled-components'; 3 | import { RouteComponentProps } from 'react-router-dom'; 4 | import { StaticContext } from 'react-router'; 5 | import BackButton from '../../components/BackButton'; 6 | import { GetTestCasesResponse } from '../../api/resultstore_download_pb'; 7 | import FlakyTestTable from '../../components/FlakyTestTable'; 8 | import TestResults, { 9 | Props as TestResultsProps, 10 | } from '../../components/TestResults'; 11 | 12 | const AppContainer = styled.div` 13 | height: 98vh; 14 | width: 98vw; 15 | margin: 0 auto 0 auto; 16 | `; 17 | 18 | const HeaderContainer = styled.div` 19 | display: flex; 20 | width: 91.5%; 21 | margin-top: 10px; 22 | margin-left: auto; 23 | margin-right: auto; 24 | `; 25 | 26 | interface RouterState { 27 | invocationsTestList: Uint8Array; 28 | } 29 | 30 | type FlakyTestState = TestResultsProps; 31 | 32 | const FlakyTest = ({ 33 | history, 34 | location, 35 | }: RouteComponentProps<{}, StaticContext, RouterState>) => { 36 | const { state } = location; 37 | const [aggregateTestData, setAggregateTestData] = useState({ 38 | totalTests: 0, 39 | failedTests: 0, 40 | passedTests: 0, 41 | }); 42 | const { totalTests, failedTests, passedTests } = aggregateTestData; 43 | const backButtonOnClick = () => { 44 | history.push('/'); 45 | }; 46 | const list = GetTestCasesResponse.deserializeBinary( 47 | state.invocationsTestList 48 | ); 49 | return ( 50 | 51 | 52 | 53 | 58 | 59 | 63 | 64 | ); 65 | }; 66 | 67 | export default FlakyTest; 68 | -------------------------------------------------------------------------------- /resultstoresearch/client/envoy/envoy.yaml: -------------------------------------------------------------------------------- 1 | admin: 2 | access_log_path: /tmp/admin_access.log 3 | address: 4 | socket_address: { address: 0.0.0.0, port_value: 9901 } 5 | 6 | static_resources: 7 | listeners: 8 | - name: listener_0 9 | address: 10 | socket_address: { address: 0.0.0.0, port_value: 8090 } 11 | filter_chains: 12 | - filters: 13 | - name: envoy.http_connection_manager 14 | config: 15 | codec_type: auto 16 | stat_prefix: ingress_http 17 | route_config: 18 | name: local_route 19 | virtual_hosts: 20 | - name: local_service 21 | domains: ['*'] 22 | routes: 23 | - match: { prefix: '/' } 24 | route: 25 | cluster: resultstore_proxy 26 | max_grpc_timeout: 0s 27 | cors: 28 | allow_origin_string_match: 29 | - prefix: '*' 30 | allow_methods: GET, PUT, DELETE, POST, OPTIONS 31 | allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout,x-goog-fieldmask,id_token 32 | max_age: '1728000' 33 | expose_headers: custom-header-1,grpc-status,grpc-message 34 | http_filters: 35 | - name: envoy.grpc_web 36 | - name: envoy.cors 37 | - name: envoy.router 38 | clusters: 39 | - name: resultstore_proxy 40 | connect_timeout: 0.25s 41 | type: logical_dns 42 | http2_protocol_options: {} 43 | lb_policy: round_robin 44 | hosts: [{ socket_address: { address: server, port_value: 9090 } }] 45 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/devtools/resultstore/v2/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright 2020 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 | # https://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 | 18 | from __future__ import absolute_import 19 | import sys 20 | import warnings 21 | 22 | from google.cloud.devtools.resultstore.v2 import types 23 | from google.cloud.devtools.resultstore.v2.gapic import enums 24 | from google.cloud.devtools.resultstore.v2.gapic import result_store_download_client 25 | from google.cloud.devtools.resultstore.v2.gapic import result_store_file_download_client 26 | from google.cloud.devtools.resultstore.v2.gapic import result_store_upload_client 27 | 28 | 29 | if sys.version_info[:2] == (2, 7): 30 | message = ( 31 | 'A future version of this library will drop support for Python 2.7.' 32 | 'More details about Python 2 support for Google Cloud Client Libraries' 33 | 'can be found at https://cloud.google.com/python/docs/python2-sunset/' 34 | ) 35 | warnings.warn(message, DeprecationWarning) 36 | 37 | class ResultStoreDownloadClient(result_store_download_client.ResultStoreDownloadClient): 38 | __doc__ = result_store_download_client.ResultStoreDownloadClient.__doc__ 39 | enums = enums 40 | 41 | class ResultStoreFileDownloadClient(result_store_file_download_client.ResultStoreFileDownloadClient): 42 | __doc__ = result_store_file_download_client.ResultStoreFileDownloadClient.__doc__ 43 | enums = enums 44 | 45 | class ResultStoreUploadClient(result_store_upload_client.ResultStoreUploadClient): 46 | __doc__ = result_store_upload_client.ResultStoreUploadClient.__doc__ 47 | enums = enums 48 | 49 | 50 | __all__ = ( 51 | 'enums', 52 | 'types', 53 | 'ResultStoreDownloadClient', 54 | 'ResultStoreFileDownloadClient', 55 | 'ResultStoreUploadClient', 56 | ) 57 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/devtools/resultstore/v2/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright 2020 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 | # https://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 | 18 | from __future__ import absolute_import 19 | import sys 20 | import warnings 21 | 22 | from google.cloud.devtools.resultstore.v2 import types 23 | from google.cloud.devtools.resultstore.v2.gapic import enums 24 | from google.cloud.devtools.resultstore.v2.gapic import result_store_download_client 25 | from google.cloud.devtools.resultstore.v2.gapic import result_store_file_download_client 26 | from google.cloud.devtools.resultstore.v2.gapic import result_store_upload_client 27 | 28 | 29 | if sys.version_info[:2] == (2, 7): 30 | message = ( 31 | 'A future version of this library will drop support for Python 2.7.' 32 | 'More details about Python 2 support for Google Cloud Client Libraries' 33 | 'can be found at https://cloud.google.com/python/docs/python2-sunset/' 34 | ) 35 | warnings.warn(message, DeprecationWarning) 36 | 37 | class ResultStoreDownloadClient(result_store_download_client.ResultStoreDownloadClient): 38 | __doc__ = result_store_download_client.ResultStoreDownloadClient.__doc__ 39 | enums = enums 40 | 41 | class ResultStoreFileDownloadClient(result_store_file_download_client.ResultStoreFileDownloadClient): 42 | __doc__ = result_store_file_download_client.ResultStoreFileDownloadClient.__doc__ 43 | enums = enums 44 | 45 | class ResultStoreUploadClient(result_store_upload_client.ResultStoreUploadClient): 46 | __doc__ = result_store_upload_client.ResultStoreUploadClient.__doc__ 47 | enums = enums 48 | 49 | 50 | __all__ = ( 51 | 'enums', 52 | 'types', 53 | 'ResultStoreDownloadClient', 54 | 'ResultStoreFileDownloadClient', 55 | 'ResultStoreUploadClient', 56 | ) 57 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/Pages/FilePage/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, useContext } from 'react'; 2 | import { RouteComponentProps } from 'react-router-dom'; 3 | import { useGoogleLogin } from 'react-google-login'; 4 | import { downloadFile, DownloadFileCallback } from '../../api/client/client'; 5 | import * as QueryString from 'query-string'; 6 | import { AuthContext } from '../../contexts/AuthContext'; 7 | import config from '../../config/ConfigLoader'; 8 | 9 | interface State { 10 | fileData: any; 11 | } 12 | 13 | const FilePage = ({ location }: RouteComponentProps) => { 14 | const [isLoggedIn, setIsLoggedIn] = useState(false); 15 | const authContext = useContext(AuthContext); 16 | const logIn = (response) => { 17 | authContext.setTokenId(response.tokenObj.id_token); 18 | setIsLoggedIn(true); 19 | }; 20 | useGoogleLogin({ 21 | isSignedIn: true, 22 | onSuccess: logIn, 23 | onFailure: () => {}, 24 | clientId: config.clientId, 25 | }); 26 | const [fileData, setFileData] = useState(''); 27 | const qs = QueryString.parse(location.search); 28 | const fileName = qs.fileName as string; 29 | const prefix = qs.prefix as string; 30 | const fullFileLocation = `${prefix}/${fileName}`; 31 | 32 | useEffect(() => { 33 | const formatResponse = (data: string) => { 34 | const splitFileName = fileName.split('.'); 35 | if (splitFileName[splitFileName.length - 1] === 'json') { 36 | return `
${JSON.stringify(
37 |                     JSON.parse(data),
38 |                     null,
39 |                     2
40 |                 )}
`; 41 | } else { 42 | return data; 43 | } 44 | }; 45 | 46 | const downloadFileCallback: DownloadFileCallback = (err, response) => { 47 | if (err) { 48 | console.error(err); 49 | } else { 50 | setFileData(formatResponse(response.getFileData())); 51 | } 52 | }; 53 | 54 | downloadFile( 55 | fullFileLocation, 56 | authContext.tokenId, 57 | downloadFileCallback 58 | ); 59 | }, [isLoggedIn, fullFileLocation, fileName, authContext.tokenId]); 60 | 61 | return
; 62 | }; 63 | 64 | export default FilePage; 65 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/components/FlakyTestTable/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import Paper from '@material-ui/core/Paper'; 3 | const TestNameCellWidth = 400; 4 | export const GridContainer = styled.div<{ width: number }>` 5 | display: flex; 6 | width: ${({ width }) => width}px; 7 | `; 8 | 9 | export const GridBodyContainer = styled.div` 10 | width: 100%; 11 | `; 12 | 13 | export const HeaderContainer = styled.div` 14 | width: ${TestNameCellWidth}px; 15 | `; 16 | 17 | export const NoTestCell = styled.div` 18 | background-color: grey; 19 | border: 1px solid black; 20 | `; 21 | 22 | export const PassingTestCell = styled.div` 23 | background-color: #4eb369; 24 | border: 1px solid black; 25 | cursor: pointer; 26 | `; 27 | 28 | export const FailingTestCell = styled.div` 29 | background-color: #d73f35; 30 | border: 1px solid black; 31 | display: flex; 32 | align-items: center; 33 | justify-content: center; 34 | text-align: center; 35 | font-size: 14px; 36 | cursor: pointer; 37 | `; 38 | 39 | export const TestNameCell = styled.div` 40 | width: ${TestNameCellWidth - 10}px !important; 41 | display: flex; 42 | align-items: center; 43 | padding-left: 10px; 44 | border-bottom: 1px solid black; 45 | border-right: 1px solid black; 46 | text-overflow: ellipsis; 47 | overflow: hidden; 48 | white-space: nowrap; 49 | `; 50 | 51 | export const TestNameDividerCell = styled.div` 52 | width: ${TestNameCellWidth - 10}px !important; 53 | display: flex; 54 | align-items: center; 55 | padding-left: 10px; 56 | border-right: 1px solid black; 57 | background-color: #e0e1dd; 58 | font-weight: bold; 59 | text-overflow: ellipsis; 60 | overflow: hidden; 61 | white-space: nowrap; 62 | `; 63 | 64 | export const DividerCell = styled.div` 65 | background-color: #e0e1dd; 66 | `; 67 | 68 | export const TitleHeaderCell = styled.div` 69 | display: flex; 70 | align-items: center; 71 | border-right: 1px solid black; 72 | `; 73 | 74 | export const HeaderCell = styled.div` 75 | display: flex; 76 | align-items: center; 77 | justify-content: center; 78 | text-align: center; 79 | `; 80 | 81 | export const Container = styled(Paper)` 82 | width: 91.5%; 83 | height: calc(98vh - 120px); 84 | margin-left: auto; 85 | margin-right: auto; 86 | margin-top: 20px; 87 | outline: none; 88 | `; 89 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/api/file_processing_error_pb.d.ts: -------------------------------------------------------------------------------- 1 | import * as jspb from "google-protobuf" 2 | 3 | export class FileProcessingErrors extends jspb.Message { 4 | getFileUid(): string; 5 | setFileUid(value: string): FileProcessingErrors; 6 | 7 | getFileProcessingErrorsList(): Array; 8 | setFileProcessingErrorsList(value: Array): FileProcessingErrors; 9 | clearFileProcessingErrorsList(): FileProcessingErrors; 10 | addFileProcessingErrors(value?: FileProcessingError, index?: number): FileProcessingError; 11 | 12 | serializeBinary(): Uint8Array; 13 | toObject(includeInstance?: boolean): FileProcessingErrors.AsObject; 14 | static toObject(includeInstance: boolean, msg: FileProcessingErrors): FileProcessingErrors.AsObject; 15 | static serializeBinaryToWriter(message: FileProcessingErrors, writer: jspb.BinaryWriter): void; 16 | static deserializeBinary(bytes: Uint8Array): FileProcessingErrors; 17 | static deserializeBinaryFromReader(message: FileProcessingErrors, reader: jspb.BinaryReader): FileProcessingErrors; 18 | } 19 | 20 | export namespace FileProcessingErrors { 21 | export type AsObject = { 22 | fileUid: string, 23 | fileProcessingErrorsList: Array, 24 | } 25 | } 26 | 27 | export class FileProcessingError extends jspb.Message { 28 | getType(): FileProcessingErrorType; 29 | setType(value: FileProcessingErrorType): FileProcessingError; 30 | 31 | getMessage(): string; 32 | setMessage(value: string): FileProcessingError; 33 | 34 | serializeBinary(): Uint8Array; 35 | toObject(includeInstance?: boolean): FileProcessingError.AsObject; 36 | static toObject(includeInstance: boolean, msg: FileProcessingError): FileProcessingError.AsObject; 37 | static serializeBinaryToWriter(message: FileProcessingError, writer: jspb.BinaryWriter): void; 38 | static deserializeBinary(bytes: Uint8Array): FileProcessingError; 39 | static deserializeBinaryFromReader(message: FileProcessingError, reader: jspb.BinaryReader): FileProcessingError; 40 | } 41 | 42 | export namespace FileProcessingError { 43 | export type AsObject = { 44 | type: FileProcessingErrorType, 45 | message: string, 46 | } 47 | } 48 | 49 | export enum FileProcessingErrorType { 50 | FILE_PROCESSING_ERROR_TYPE_UNSPECIFIED = 0, 51 | GENERIC_READ_ERROR = 1, 52 | GENERIC_PARSE_ERROR = 2, 53 | FILE_TOO_LARGE = 3, 54 | OUTPUT_TOO_LARGE = 4, 55 | ACCESS_DENIED = 5, 56 | DEADLINE_EXCEEDED = 6, 57 | NOT_FOUND = 7, 58 | FILE_EMPTY = 8, 59 | } 60 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/components/GoogleButton/index.tsx: -------------------------------------------------------------------------------- 1 | // BackButton Component 2 | /** 3 | * Component that handles user login / logout via google sign-in 4 | * @packageDocumentation 5 | */ 6 | import React, { useState, useContext } from 'react'; 7 | import { GoogleLogout, GoogleLogin } from 'react-google-login'; 8 | import styled from 'styled-components'; 9 | import config from '../../config/ConfigLoader'; 10 | import { AuthContext } from '../../contexts/AuthContext'; 11 | 12 | const CustomGoogleLogin = styled(GoogleLogin)` 13 | width: 100px; 14 | height: 56px; 15 | margin-top: auto; 16 | margin-bottom: auto; 17 | margin-right: 5px; 18 | `; 19 | 20 | const CustomGoogleLogout = styled(GoogleLogout)` 21 | width: 100px; 22 | height: 56px; 23 | margin-top: auto; 24 | margin-bottom: auto; 25 | margin-right: 5px; 26 | `; 27 | 28 | /** GoogleButton State */ 29 | interface State { 30 | /** True if user is logged in*/ 31 | isLoggedIn: boolean; 32 | } 33 | 34 | /* 35 | Button that handles google login and logout 36 | */ 37 | export const GoogleButton: React.FC = () => { 38 | const authContext = useContext(AuthContext); 39 | const [isLoggedIn, setIsLoggedIn] = useState(false); 40 | 41 | const logIn = (response) => { 42 | authContext.setTokenId(response.tokenObj.id_token); 43 | setIsLoggedIn(true); 44 | }; 45 | 46 | const logOut = () => { 47 | authContext.setTokenId(''); 48 | setIsLoggedIn(false); 49 | }; 50 | 51 | const handleError = () => {}; 52 | 53 | return ( 54 | <> 55 | {!isLoggedIn && ( 56 | 65 | )} 66 | {isLoggedIn && ( 67 | 75 | )} 76 | 77 | ); 78 | }; 79 | 80 | export default GoogleButton; 81 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/auth/auth_interceptor.py: -------------------------------------------------------------------------------- 1 | """ 2 | gRPC Interceptor that verifies that users are signed in with google 3 | and have the appropriate permissions to make requests to resultstore instance 4 | """ 5 | import grpc 6 | from google.oauth2 import id_token 7 | from google.auth.transport import requests 8 | 9 | 10 | def _unary_unary_rpc_terminator(code, details): 11 | def terminate(ignored_request, context): 12 | context.abort(code, details) 13 | 14 | return grpc.unary_unary_rpc_method_handler(terminate) 15 | 16 | 17 | class AuthInterceptor(grpc.ServerInterceptor): 18 | """ 19 | Interceptor to verify that client calls are authenticated 20 | """ 21 | def __init__(self, code, details, creds): 22 | self._creds = creds 23 | self._terminator = _unary_unary_rpc_terminator(code, details) 24 | 25 | def intercept_service(self, continuation, handler_call_details): 26 | """ 27 | Intercepts incoming RPCs before handing them over to a handler. 28 | 29 | Args: 30 | continuation: A function that takes a HandlerCallDetails and 31 | proceeds to invoke the next interceptor in the chain, if any, 32 | or the RPC handler lookup logic, with the call details passed 33 | as an argument, and returns an RpcMethodHandler instance if 34 | the RPC is considered serviced, or None otherwise. 35 | handler_call_details: A HandlerCallDetails describing the RPC. 36 | 37 | Returns: 38 | An RpcMethodHandler with which the RPC may be serviced if the 39 | interceptor chooses to service this RPC, or None otherwise. 40 | """ 41 | metadata = dict(handler_call_details.invocation_metadata) 42 | if 'id_token' not in metadata: 43 | return self._terminator 44 | token = metadata['id_token'] 45 | 46 | try: 47 | idinfo = id_token.verify_oauth2_token(token, requests.Request(), 48 | self._creds.get_client_id()) 49 | if idinfo['iss'] not in [ 50 | 'accounts.google.com', 'https://accounts.google.com' 51 | ]: 52 | raise ValueError('Wrong issuer.') 53 | user_email = idinfo['email'] 54 | except ValueError: 55 | return self._terminator 56 | 57 | verified = self._creds.verify_user(user_email) 58 | if verified: 59 | return continuation(handler_call_details) 60 | return self._terminator 61 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/components/InvocationTable/utils.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | import invocation_pb from '../../api/invocation_pb'; 3 | import common_pb from '../../api/common_pb'; 4 | import { Data } from './types'; 5 | import { getDate } from '../../utils'; 6 | 7 | /* 8 | Parses invocation returned from search into invocation table consumable data 9 | 10 | Args: 11 | Invocation: An invocation to be parsed 12 | 13 | Returns: 14 | Table consumable object 15 | */ 16 | export const parseInvocationTableData = ( 17 | invocation: invocation_pb.Invocation 18 | ): Data => { 19 | const statusAttributes = invocation.getStatusAttributes(); 20 | const invocationAttributes = invocation.getInvocationAttributes(); 21 | const workspaceInfo = invocation.getWorkspaceInfo(); 22 | const timing = invocation.getTiming(); 23 | 24 | const status = getStatus(statusAttributes); 25 | const name = invocation.getName(); 26 | const labels = getLabels(invocationAttributes); 27 | const date = getDate(timing); 28 | const duration = getDuration(timing); 29 | const user = getUser(invocationAttributes, workspaceInfo); 30 | 31 | return { status, name, labels, date, duration, user }; 32 | }; 33 | 34 | /* 35 | Helper functions to format invocation table data 36 | */ 37 | 38 | const getUser = ( 39 | invocationAttributes: invocation_pb.InvocationAttributes | undefined, 40 | workspaceInfo: invocation_pb.WorkspaceInfo | undefined 41 | ) => { 42 | const hostname = (workspaceInfo && workspaceInfo.getHostname()) || ''; 43 | const usersList = (invocationAttributes && 44 | invocationAttributes.getUsersList()) || ['None']; 45 | const userPrefix = (usersList.length > 0 && usersList[0]) || 'None'; 46 | return `${userPrefix}@${hostname}`; 47 | }; 48 | 49 | const getStatus = ( 50 | statusAttributes: common_pb.StatusAttributes | undefined 51 | ) => { 52 | return (statusAttributes && statusAttributes.getDescription()) || 'UNKNOWN'; 53 | }; 54 | 55 | const getLabels = ( 56 | invocationAttributes: invocation_pb.InvocationAttributes 57 | ) => { 58 | const labelsList = 59 | (invocationAttributes && invocationAttributes.getLabelsList()) || []; 60 | return labelsList.join(','); 61 | }; 62 | 63 | const getDuration = (timing: common_pb.Timing | undefined) => { 64 | const timingDuration = timing.getDuration(); 65 | const durationSeconds = 66 | (timingDuration && timingDuration.getSeconds()) || 0; 67 | return moment 68 | .utc(moment.duration(durationSeconds, 's').asMilliseconds()) 69 | .format('mm:ss'); 70 | }; 71 | -------------------------------------------------------------------------------- /resultstoreui/bigstore_client.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pathlib 3 | from google import auth 4 | from google.cloud import storage 5 | from resultstoreapi.cloud.devtools.resultstore_v2.proto import (file_pb2) 6 | 7 | 8 | class BigStoreClient(object): 9 | """ Client for BigStore""" 10 | def __init__(self, credentials, project_name, storage_dir, bucket_name): 11 | """ 12 | Initialize BigStore Client 13 | 14 | Args: 15 | credentials (Credentials): credentials and scope for the client 16 | project_name (str): Porject name to connect to 17 | storage_dir (str): Specify directory to store uploaded files 18 | bucket_name (str): Bucket name to upload files to 19 | 20 | Return: 21 | List of files that were uploaded 22 | """ 23 | self.credentials = credentials 24 | self.storage_dir = storage_dir 25 | self.bucket_name = bucket_name 26 | self.project_name = project_name 27 | 28 | def upload_files_to_bigstore(self, files): 29 | """ 30 | Upload files to bigstore (aka google cloud storage). 31 | 32 | Args: 33 | files (Sequence[str]): list of file path names 34 | storage_dir (str): cloud storage dir where files should be saved 35 | bucket_name (str): cloud storage bucket name 36 | 37 | Returns: 38 | uploaded_files: A list of file_pb2.File() objects 39 | 40 | Raises: 41 | FileNotFoundError: if a file is missing. 42 | """ 43 | prefix = self.storage_dir 44 | credentials = auth.default(scopes=self.credentials.get_scopes()) 45 | 46 | # Create the client using the credentials and specifying a project ID. 47 | storage_client = storage.Client(credentials=credentials[0], 48 | project=self.project_name) 49 | bucket = storage_client.get_bucket(self.bucket_name) 50 | uploaded_files = [] 51 | 52 | for pathname in files: 53 | path = pathlib.Path(pathname) 54 | if not path.exists() or not path.is_file(): 55 | raise FileNotFoundError(pathname) 56 | filename = pathname.split('/')[-1] 57 | blob = bucket.blob(os.path.join(prefix, filename)) 58 | blob.upload_from_filename(pathname) 59 | file_object = file_pb2.File( 60 | uid=filename, 61 | uri='googlefile:/bigstore/{}/{}'.format( 62 | self.bucket_name, prefix + filename)) 63 | uploaded_files.append(file_object) 64 | return uploaded_files 65 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoresearchapi/duration_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: duration.proto 4 | 5 | from google.protobuf import descriptor as _descriptor 6 | from google.protobuf import message as _message 7 | from google.protobuf import reflection as _reflection 8 | from google.protobuf import symbol_database as _symbol_database 9 | # @@protoc_insertion_point(imports) 10 | 11 | _sym_db = _symbol_database.Default() 12 | 13 | 14 | 15 | 16 | DESCRIPTOR = _descriptor.FileDescriptor( 17 | name='duration.proto', 18 | package='resultstoresearch.v1', 19 | syntax='proto3', 20 | serialized_options=None, 21 | create_key=_descriptor._internal_create_key, 22 | serialized_pb=b'\n\x0e\x64uration.proto\x12\x14resultstoresearch.v1\"*\n\x08\x44uration\x12\x0f\n\x07seconds\x18\x01 \x01(\x03\x12\r\n\x05nanos\x18\x02 \x01(\x05\x62\x06proto3' 23 | ) 24 | 25 | 26 | 27 | 28 | _DURATION = _descriptor.Descriptor( 29 | name='Duration', 30 | full_name='resultstoresearch.v1.Duration', 31 | filename=None, 32 | file=DESCRIPTOR, 33 | containing_type=None, 34 | create_key=_descriptor._internal_create_key, 35 | fields=[ 36 | _descriptor.FieldDescriptor( 37 | name='seconds', full_name='resultstoresearch.v1.Duration.seconds', index=0, 38 | number=1, type=3, cpp_type=2, label=1, 39 | has_default_value=False, default_value=0, 40 | message_type=None, enum_type=None, containing_type=None, 41 | is_extension=False, extension_scope=None, 42 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 43 | _descriptor.FieldDescriptor( 44 | name='nanos', full_name='resultstoresearch.v1.Duration.nanos', index=1, 45 | number=2, type=5, cpp_type=1, label=1, 46 | has_default_value=False, default_value=0, 47 | message_type=None, enum_type=None, containing_type=None, 48 | is_extension=False, extension_scope=None, 49 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 50 | ], 51 | extensions=[ 52 | ], 53 | nested_types=[], 54 | enum_types=[ 55 | ], 56 | serialized_options=None, 57 | is_extendable=False, 58 | syntax='proto3', 59 | extension_ranges=[], 60 | oneofs=[ 61 | ], 62 | serialized_start=40, 63 | serialized_end=82, 64 | ) 65 | 66 | DESCRIPTOR.message_types_by_name['Duration'] = _DURATION 67 | _sym_db.RegisterFileDescriptor(DESCRIPTOR) 68 | 69 | Duration = _reflection.GeneratedProtocolMessageType('Duration', (_message.Message,), { 70 | 'DESCRIPTOR' : _DURATION, 71 | '__module__' : 'duration_pb2' 72 | # @@protoc_insertion_point(class_scope:resultstoresearch.v1.Duration) 73 | }) 74 | _sym_db.RegisterMessage(Duration) 75 | 76 | 77 | # @@protoc_insertion_point(module_scope) 78 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoresearchapi/timestamp_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: timestamp.proto 4 | 5 | from google.protobuf import descriptor as _descriptor 6 | from google.protobuf import message as _message 7 | from google.protobuf import reflection as _reflection 8 | from google.protobuf import symbol_database as _symbol_database 9 | # @@protoc_insertion_point(imports) 10 | 11 | _sym_db = _symbol_database.Default() 12 | 13 | 14 | 15 | 16 | DESCRIPTOR = _descriptor.FileDescriptor( 17 | name='timestamp.proto', 18 | package='resultstoresearch.v1', 19 | syntax='proto3', 20 | serialized_options=None, 21 | create_key=_descriptor._internal_create_key, 22 | serialized_pb=b'\n\x0ftimestamp.proto\x12\x14resultstoresearch.v1\"+\n\tTimestamp\x12\x0f\n\x07seconds\x18\x01 \x01(\x03\x12\r\n\x05nanos\x18\x02 \x01(\x05\x62\x06proto3' 23 | ) 24 | 25 | 26 | 27 | 28 | _TIMESTAMP = _descriptor.Descriptor( 29 | name='Timestamp', 30 | full_name='resultstoresearch.v1.Timestamp', 31 | filename=None, 32 | file=DESCRIPTOR, 33 | containing_type=None, 34 | create_key=_descriptor._internal_create_key, 35 | fields=[ 36 | _descriptor.FieldDescriptor( 37 | name='seconds', full_name='resultstoresearch.v1.Timestamp.seconds', index=0, 38 | number=1, type=3, cpp_type=2, label=1, 39 | has_default_value=False, default_value=0, 40 | message_type=None, enum_type=None, containing_type=None, 41 | is_extension=False, extension_scope=None, 42 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 43 | _descriptor.FieldDescriptor( 44 | name='nanos', full_name='resultstoresearch.v1.Timestamp.nanos', index=1, 45 | number=2, type=5, cpp_type=1, label=1, 46 | has_default_value=False, default_value=0, 47 | message_type=None, enum_type=None, containing_type=None, 48 | is_extension=False, extension_scope=None, 49 | serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), 50 | ], 51 | extensions=[ 52 | ], 53 | nested_types=[], 54 | enum_types=[ 55 | ], 56 | serialized_options=None, 57 | is_extendable=False, 58 | syntax='proto3', 59 | extension_ranges=[], 60 | oneofs=[ 61 | ], 62 | serialized_start=41, 63 | serialized_end=84, 64 | ) 65 | 66 | DESCRIPTOR.message_types_by_name['Timestamp'] = _TIMESTAMP 67 | _sym_db.RegisterFileDescriptor(DESCRIPTOR) 68 | 69 | Timestamp = _reflection.GeneratedProtocolMessageType('Timestamp', (_message.Message,), { 70 | 'DESCRIPTOR' : _TIMESTAMP, 71 | '__module__' : 'timestamp_pb2' 72 | # @@protoc_insertion_point(class_scope:resultstoresearch.v1.Timestamp) 73 | }) 74 | _sym_db.RegisterMessage(Timestamp) 75 | 76 | 77 | # @@protoc_insertion_point(module_scope) 78 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/components/SearchWrapper/ToolSelect/index.tsx: -------------------------------------------------------------------------------- 1 | // ToolSelect Component 2 | /** 3 | * Dropdown to filter invocation search on tooltype 4 | * @packageDocumentation 5 | */ 6 | import React, { useEffect } from 'react'; 7 | import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; 8 | import FormControl from '@material-ui/core/FormControl'; 9 | import InputLabel from '@material-ui/core/InputLabel'; 10 | import Select from '@material-ui/core/Select'; 11 | import MenuItem from '@material-ui/core/MenuItem'; 12 | import { getInitialState } from '../../../api/client/client'; 13 | 14 | /** ToolSelect Props */ 15 | export interface ToolSelectProps { 16 | /** List of tools available for selection in dropdown */ 17 | toolsList: Array; 18 | /** Set the list of tools available for selection in the dropdown */ 19 | setToolsList: (toolsList: Array) => void; 20 | /** Currently selected tool */ 21 | selectedTool: string; 22 | /** Callback fired when a tool is selected from the dropdown */ 23 | setSelectedTool: (selectedTool: string) => void; 24 | } 25 | 26 | const useStyles = makeStyles((theme: Theme) => 27 | createStyles({ 28 | formControl: { 29 | margin: theme.spacing(1), 30 | minWidth: 120, 31 | }, 32 | }) 33 | ); 34 | 35 | const createToolItems = (toolsList: ToolSelectProps['toolsList']) => { 36 | return toolsList.map((tool, index) => ( 37 | 38 | {tool} 39 | 40 | )); 41 | }; 42 | 43 | /** Tool Select Component */ 44 | export const ToolSelect: React.FC = ({ 45 | selectedTool, 46 | setSelectedTool, 47 | toolsList, 48 | setToolsList, 49 | }) => { 50 | const classes = useStyles(); 51 | useEffect(() => { 52 | getInitialState(setToolsList); 53 | }, [setToolsList]); 54 | 55 | return ( 56 | 61 | 62 | Tool Type 63 | 64 | 78 | 79 | ); 80 | }; 81 | 82 | export default ToolSelect; 83 | -------------------------------------------------------------------------------- /resultstoresearch/e2e/cypress/integration/app.spec.ts: -------------------------------------------------------------------------------- 1 | context("ResultStoreSearch Home Page", () => { 2 | beforeEach(() => { 3 | cy.visit("/"); 4 | }); 5 | 6 | it("Should display an error if the query is invalid", () => { 7 | cy.get('input[id="search-bar"]').type("incorrect query"); 8 | cy.get('input[id="search-bar"]').type("{enter}"); 9 | cy.get('p[id="search-error"]').contains("Invalid query string"); 10 | }); 11 | 12 | it("Should render the invocations in the table", () => { 13 | cy.get('input[id="search-bar"]').type("a"); 14 | cy.get('input[id="search-bar"]').type("{enter}"); 15 | cy.get('div[aria-label="row"]').contains( 16 | "invocations/51be7217-9798-4448-adf8-1e4428c71e9e" 17 | ); 18 | cy.get('div[aria-label="row"]').contains("TESTING"); 19 | cy.get('div[aria-label="row"]').contains("dank,meme"); 20 | cy.get('div[aria-label="row"]').contains("00:06"); 21 | cy.get('div[aria-label="row"]').contains("lewiscraig@"); 22 | }); 23 | 24 | it("Should display tool1 and tool2 in dropdown and have 1 invocation", () => { 25 | cy.get('input[id="search-bar"]').type("a"); 26 | cy.get('input[id="search-bar"]').type("{enter}"); 27 | cy.get('div[id="tool-select"]').click(); 28 | cy.get('li[data-value="tool1"]').contains("tool1"); 29 | cy.get('li[data-value="tool2"').contains("tool2"); 30 | cy.get('li[data-value="tool2"]').click(); 31 | cy.get('input[id="search-bar"]').type("i"); 32 | cy.get('input[id="search-bar"]').type("{enter}"); 33 | cy.get('input[id="search-bar"]').type("i"); 34 | cy.get('input[id="search-bar"]').type("{enter}"); 35 | cy.get('div[id="InvocationTable"]') 36 | .find('div[aria-label="row"]') 37 | .should("have.length", 1); 38 | }); 39 | 40 | it("Should display tooltip on hover", () => { 41 | cy.get('svg[aria-label="SearchHelp"]').trigger("mouseover"); 42 | cy.get('div[class="MuiTooltip-popper"]').contains( 43 | 'Fields that support equals ("=") restrictions:' 44 | ); 45 | }); 46 | 47 | it("Should open the file modal on file button click", () => { 48 | cy.get('input[id="search-bar"]').type("a"); 49 | cy.get('input[id="search-bar"]').type("{enter}"); 50 | cy.get('button[title="Close"]').click(); 51 | cy.get('div[aria-rowindex="1"]').trigger("mouseover"); 52 | cy.get('button[id="FileButton-0"]').click(); 53 | cy.get('div[id="FileModal"]').should("have.length", 1); 54 | }); 55 | 56 | it("Should render files in the file table", () => { 57 | cy.get('input[id="search-bar"]').type("a"); 58 | cy.get('input[id="search-bar"]').type("{enter}"); 59 | cy.get('button[title="Close"]').click(); 60 | cy.get('div[aria-rowindex="1"]').trigger("mouseover"); 61 | cy.get('button[id="FileButton-0"]').click(); 62 | cy.get('div[id="InvocationModalRow"]').click(); 63 | cy.get('div[id="FileTable"]').contains("test-file"); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/mock/mock_proxy.py: -------------------------------------------------------------------------------- 1 | from resultstoresearch.resultstoresearchapi import ( 2 | resultstore_download_pb2_grpc, resultstore_download_pb2) 3 | from resultstoresearch.mock.mock_responses import ( 4 | mock_search_invocations_response, 5 | mock_search_invocations_response_with_tools, mock_file, mock_target) 6 | import logging 7 | import grpc 8 | import os 9 | 10 | 11 | class MockProxyServer(resultstore_download_pb2_grpc.ResultStoreDownloadServicer 12 | ): 13 | def SearchInvocations(self, request, context): 14 | """ 15 | Proxies the SearchInvocations gRPC call forward 16 | 17 | Args: 18 | request (SearchInvocationsRequest): The search request 19 | context (grpc.Context) 20 | 21 | Returns: 22 | SearchInvocationsResponse 23 | """ 24 | if request.query == 'incorrect query': 25 | context.set_details('Invalid query string') 26 | context.set_code(grpc.StatusCode.INVALID_ARGUMENT) 27 | return resultstore_download_pb2.SearchInvocationsResponse() 28 | elif request.tool: 29 | return mock_search_invocations_response_with_tools() 30 | return mock_search_invocations_response() 31 | 32 | def ListTargets(self, request, context): 33 | """ 34 | Mocks listing of targets for a given invocation 35 | 36 | Args: 37 | request (ListTargetsRequest): The ListTargets request 38 | context (grpc.Context) 39 | 40 | Returns: 41 | ListTargetsResponse 42 | """ 43 | return [mock_target()] 44 | 45 | def ListTargetSubFiles(self, request, context): 46 | """ 47 | Mocks listing of files under a given target 48 | 49 | Args: 50 | request (ListTargetSubFilesRequest): The ListTargetSubFiles request 51 | context (grpc.Context) 52 | 53 | Returns: 54 | ListTargetSubFilesResponse 55 | """ 56 | return [mock_file(), mock_file()] 57 | 58 | def GetInitialState(self, request, context): 59 | """ 60 | Gets the initial state of the client 61 | 62 | Args: 63 | request (GetInitialStateRequest): The initial state request 64 | context (grpc.Context) 65 | 66 | Returns: 67 | GetInitialStateResponse 68 | """ 69 | return resultstore_download_pb2.GetInitialStateResponse( 70 | tools_list=['tool1', 'tool2']) 71 | 72 | def DownloadFile(self, request, context): 73 | """ 74 | Mocks Downloading a file from bigstore 75 | 76 | Args: 77 | request (DownloadFileRequest): The DownloadFile request 78 | context (grpc.Context) 79 | 80 | Returns: 81 | DownloadFileResponse 82 | """ 83 | with open('./mock/dummy_file.html', 'r') as file: 84 | data = file.read() 85 | return resultstore_download_pb2.DownloadFileResponse( 86 | file_data=data) 87 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/mock/mock_responses.py: -------------------------------------------------------------------------------- 1 | from resultstoresearch.resultstoresearchapi import ( 2 | invocation_pb2, common_pb2, timestamp_pb2, duration_pb2, 3 | resultstore_download_pb2, file_pb2, wrappers_pb2, target_pb2) 4 | """ 5 | Used by mock proxy to generate mock responses 6 | """ 7 | 8 | 9 | def mock_search_invocations_response(): 10 | response = resultstore_download_pb2.SearchInvocationsResponse( 11 | invocations=[mock_invocation(), 12 | mock_invocation('invo1', 'tool1')], 13 | tools_list=['tool1', 'tool2']) 14 | return response 15 | 16 | 17 | def mock_search_invocations_response_with_tools(): 18 | response = resultstore_download_pb2.SearchInvocationsResponse( 19 | invocations=[mock_invocation('invo1', 'tool1')], 20 | tools_list=['tool1', 'tool2']) 21 | return response 22 | 23 | 24 | def mock_invocation( 25 | invocation_name='invocations/51be7217-9798-4448-adf8-1e4428c71e9e', 26 | tool_tag=''): 27 | invocation = invocation_pb2.Invocation( 28 | name=invocation_name, 29 | status_attributes=mock_status_attributes(), 30 | timing=mock_timing(), 31 | invocation_attributes=mock_invocation_attributes(), 32 | workspace_info=mock_workspace_info(tool_tag), 33 | files=[mock_file()]) 34 | return invocation 35 | 36 | 37 | def mock_status_attributes(): 38 | status_attributes = common_pb2.StatusAttributes( 39 | status=common_pb2.Status.Value('TESTING'), description='TESTING') 40 | return status_attributes 41 | 42 | 43 | def mock_timing(): 44 | timing = common_pb2.Timing(start_time=mock_timestamp(), 45 | duration=mock_duration()) 46 | return timing 47 | 48 | 49 | def mock_timestamp(): 50 | timestamp = timestamp_pb2.Timestamp(seconds=1589929143, nanos=0) 51 | return timestamp 52 | 53 | 54 | def mock_duration(): 55 | duration = duration_pb2.Duration(seconds=6, nanos=0) 56 | return duration 57 | 58 | 59 | def mock_invocation_attributes(): 60 | invocation_attributes = invocation_pb2.InvocationAttributes( 61 | users=(['lewiscraig']), labels=(['dank', 'meme'])) 62 | return invocation_attributes 63 | 64 | 65 | def mock_workspace_info(tool_tag=''): 66 | workspace_info = invocation_pb2.WorkspaceInfo( 67 | hostname='piestation.mtv.corp.google.com', 68 | command_lines=mock_command_lines(), 69 | tool_tag=tool_tag) 70 | return workspace_info 71 | 72 | 73 | def mock_command_lines(): 74 | command_line = invocation_pb2.CommandLine(label='SiVal Test Runner', 75 | tool='ManifestRunner') 76 | return [command_line] 77 | 78 | 79 | def mock_target(): 80 | return target_pb2.Target(name='mock-target', files=[mock_file()]) 81 | 82 | 83 | def mock_file(): 84 | return file_pb2.File(uid='test-file', 85 | length=mock_int_64_value(), 86 | content_type='text/html') 87 | 88 | 89 | def mock_int_64_value(): 90 | return wrappers_pb2.Int64Value(value=45) 91 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/api/file_pb.d.ts: -------------------------------------------------------------------------------- 1 | import * as jspb from "google-protobuf" 2 | 3 | import * as wrappers_pb from './wrappers_pb'; 4 | 5 | export class File extends jspb.Message { 6 | getUid(): string; 7 | setUid(value: string): File; 8 | 9 | getUri(): string; 10 | setUri(value: string): File; 11 | 12 | getLength(): wrappers_pb.Int64Value | undefined; 13 | setLength(value?: wrappers_pb.Int64Value): File; 14 | hasLength(): boolean; 15 | clearLength(): File; 16 | 17 | getContentType(): string; 18 | setContentType(value: string): File; 19 | 20 | getArchiveEntry(): ArchiveEntry | undefined; 21 | setArchiveEntry(value?: ArchiveEntry): File; 22 | hasArchiveEntry(): boolean; 23 | clearArchiveEntry(): File; 24 | 25 | getContentViewer(): string; 26 | setContentViewer(value: string): File; 27 | 28 | getHidden(): boolean; 29 | setHidden(value: boolean): File; 30 | 31 | getDescription(): string; 32 | setDescription(value: string): File; 33 | 34 | getDigest(): string; 35 | setDigest(value: string): File; 36 | 37 | getHashType(): File.HashType; 38 | setHashType(value: File.HashType): File; 39 | 40 | serializeBinary(): Uint8Array; 41 | toObject(includeInstance?: boolean): File.AsObject; 42 | static toObject(includeInstance: boolean, msg: File): File.AsObject; 43 | static serializeBinaryToWriter(message: File, writer: jspb.BinaryWriter): void; 44 | static deserializeBinary(bytes: Uint8Array): File; 45 | static deserializeBinaryFromReader(message: File, reader: jspb.BinaryReader): File; 46 | } 47 | 48 | export namespace File { 49 | export type AsObject = { 50 | uid: string, 51 | uri: string, 52 | length?: wrappers_pb.Int64Value.AsObject, 53 | contentType: string, 54 | archiveEntry?: ArchiveEntry.AsObject, 55 | contentViewer: string, 56 | hidden: boolean, 57 | description: string, 58 | digest: string, 59 | hashType: File.HashType, 60 | } 61 | 62 | export enum HashType { 63 | HASH_TYPE_UNSPECIFIED = 0, 64 | MD5 = 1, 65 | SHA1 = 2, 66 | SHA256 = 3, 67 | } 68 | } 69 | 70 | export class ArchiveEntry extends jspb.Message { 71 | getPath(): string; 72 | setPath(value: string): ArchiveEntry; 73 | 74 | getLength(): wrappers_pb.Int64Value | undefined; 75 | setLength(value?: wrappers_pb.Int64Value): ArchiveEntry; 76 | hasLength(): boolean; 77 | clearLength(): ArchiveEntry; 78 | 79 | getContentType(): string; 80 | setContentType(value: string): ArchiveEntry; 81 | 82 | serializeBinary(): Uint8Array; 83 | toObject(includeInstance?: boolean): ArchiveEntry.AsObject; 84 | static toObject(includeInstance: boolean, msg: ArchiveEntry): ArchiveEntry.AsObject; 85 | static serializeBinaryToWriter(message: ArchiveEntry, writer: jspb.BinaryWriter): void; 86 | static deserializeBinary(bytes: Uint8Array): ArchiveEntry; 87 | static deserializeBinaryFromReader(message: ArchiveEntry, reader: jspb.BinaryReader): ArchiveEntry; 88 | } 89 | 90 | export namespace ArchiveEntry { 91 | export type AsObject = { 92 | path: string, 93 | length?: wrappers_pb.Int64Value.AsObject, 94 | contentType: string, 95 | } 96 | } 97 | 98 | -------------------------------------------------------------------------------- /resultstoresearch/client/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `yarn start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `yarn test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `yarn build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `yarn eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | 46 | ### Code Splitting 47 | 48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 49 | 50 | ### Analyzing the Bundle Size 51 | 52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 53 | 54 | ### Making a Progressive Web App 55 | 56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 57 | 58 | ### Advanced Configuration 59 | 60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 61 | 62 | ### Deployment 63 | 64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 65 | 66 | ### `yarn build` fails to minify 67 | 68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/bazel 2 | # Edit at https://www.gitignore.io/?templates=bazel 3 | 4 | ### Bazel ### 5 | # gitignore template for Bazel build system 6 | # website: https://bazel.build/ 7 | 8 | # Ignore all bazel-* symlinks. There is no full list since this can change 9 | # based on the name of the directory bazel is cloned into. 10 | /bazel-* 11 | 12 | # End of https://www.gitignore.io/api/bazel 13 | 14 | # Created by https://www.gitignore.io/api/python 15 | # Edit at https://www.gitignore.io/?templates=python 16 | 17 | ### Python ### 18 | # Byte-compiled / optimized / DLL files 19 | __pycache__/ 20 | *.py[cod] 21 | *$py.class 22 | 23 | # C extensions 24 | *.so 25 | 26 | # Distribution / packaging 27 | .Python 28 | build/ 29 | develop-eggs/ 30 | dist/ 31 | downloads/ 32 | eggs/ 33 | .eggs/ 34 | lib/ 35 | lib64/ 36 | parts/ 37 | sdist/ 38 | var/ 39 | wheels/ 40 | pip-wheel-metadata/ 41 | share/python-wheels/ 42 | *.egg-info/ 43 | .installed.cfg 44 | *.egg 45 | MANIFEST 46 | 47 | # PyInstaller 48 | # Usually these files are written by a python script from a template 49 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 50 | *.manifest 51 | *.spec 52 | 53 | # Installer logs 54 | pip-log.txt 55 | pip-delete-this-directory.txt 56 | 57 | # Unit test / coverage reports 58 | htmlcov/ 59 | .tox/ 60 | .nox/ 61 | .coverage 62 | .coverage.* 63 | .cache 64 | nosetests.xml 65 | coverage.xml 66 | *.cover 67 | .hypothesis/ 68 | .pytest_cache/ 69 | 70 | # Translations 71 | *.mo 72 | *.pot 73 | 74 | # Scrapy stuff: 75 | .scrapy 76 | 77 | # Sphinx documentation 78 | docs/_build/ 79 | 80 | # PyBuilder 81 | target/ 82 | 83 | # pyenv 84 | .python-version 85 | 86 | # pipenv 87 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 88 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 89 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 90 | # install all needed dependencies. 91 | #Pipfile.lock 92 | 93 | # celery beat schedule file 94 | celerybeat-schedule 95 | 96 | # SageMath parsed files 97 | *.sage.py 98 | 99 | # Spyder project settings 100 | .spyderproject 101 | .spyproject 102 | 103 | # Rope project settings 104 | .ropeproject 105 | 106 | # Mr Developer 107 | .mr.developer.cfg 108 | .project 109 | .pydevproject 110 | 111 | # mkdocs documentation 112 | /site 113 | 114 | # mypy 115 | .mypy_cache/ 116 | .dmypy.json 117 | dmypy.json 118 | 119 | # Pyre type checker 120 | .pyre/ 121 | 122 | # Node Modules 123 | resultstoresearch/client/node_modules 124 | resultstoresearch/e2e/node_modules 125 | resultstoresearch/e2e/cypress/videos 126 | resultstoresearch/e2e/cypress/screenshots 127 | 128 | # vscode 129 | /.vscode 130 | 131 | # Docker 132 | resultstoresearch/ci/.env 133 | 134 | # Cypress 135 | resultstoresearch/e2e/cypress/screenshots 136 | resultstoresearch/e2e/cypress/videos 137 | 138 | # Docs 139 | resultstoresearch/client/docs 140 | 141 | # End of https://www.gitignore.io/api/python 142 | 143 | .env 144 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/components/StatusIcon/index.tsx: -------------------------------------------------------------------------------- 1 | // StatusIcon Component 2 | /** 3 | * Icon used to signify status of an invocation 4 | * @packageDocumentation 5 | */ 6 | import React from 'react'; 7 | import CheckIcon from '@material-ui/icons/Check'; 8 | import BuildIcon from '@material-ui/icons/Build'; 9 | import DirectionsRunIcon from '@material-ui/icons/DirectionsRun'; 10 | import TimerIcon from '@material-ui/icons/Timer'; 11 | import HelpOutlineIcon from '@material-ui/icons/HelpOutline'; 12 | import ErrorIcon from '@material-ui/icons/Error'; 13 | 14 | /** StatusIcon Props */ 15 | interface Props { 16 | /** Type of status to be displayed */ 17 | name: 18 | | 'PASS' 19 | | 'BUILDING' 20 | | 'BUILT' 21 | | 'FAILED_TO_BUILD' 22 | | 'TESTING' 23 | | 'FAILED' 24 | | 'TIMED_OUT' 25 | | 'CANCELLED' 26 | | 'TOOL_FAILED' 27 | | 'UNKNOWN' 28 | | 'SKIPPED'; 29 | /** Size of display icon svg */ 30 | size?: number; 31 | /** Display icon style */ 32 | style?: React.CSSProperties; 33 | } 34 | 35 | const StatusIcon: React.FC = ({ name, style }) => { 36 | switch (name) { 37 | case 'PASS': { 38 | return ( 39 | 44 | ); 45 | } 46 | case 'BUILDING': { 47 | return ( 48 | 53 | ); 54 | } 55 | case 'TESTING': { 56 | return ( 57 | 62 | ); 63 | } 64 | case 'TIMED_OUT': { 65 | return ( 66 | 71 | ); 72 | } 73 | case 'UNKNOWN': { 74 | return ( 75 | 80 | ); 81 | } 82 | case 'FAILED': { 83 | return ( 84 | 89 | ); 90 | } 91 | default: { 92 | return ( 93 | 98 | ); 99 | } 100 | } 101 | }; 102 | 103 | export default StatusIcon; 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Resultstore Search 2 | 3 | Service to search and render invocations and tests in resultstore. 4 | 5 | ## Client Documentation 6 | 7 | Client component documentation can be viewed [here](https://google.github.io/resultstoreui/) 8 | 9 | ## Prerequisites 10 | 11 | 1. Docker and docker-compose installed [here](https://www.docker.com/) 12 | 2. Generate [service account](https://cloud.google.com/iam/docs/creating-managing-service-account-keys) 13 | 3. Enable the following permissions on the service account 14 | 15 | ``` 16 | Cloud Source Tools Core - Developer 17 | Cloud Datastore User 18 | Storage Object Viewer 19 | Viewer 20 | ``` 21 | 22 | 4. Generate a [client id](https://developers.google.com/identity/sign-in/web/sign-in#create_authorization_credentials) 23 | 5. Configure .env file in ./resultstoresearch/ci following example.env 24 | 6. Add eligible users to the cloudsourcetoolscore.developer role in your GCP project 25 | 26 | ## Usage 27 | 28 | Locally: 29 | 30 | ```shell 31 | ./startup 32 | ``` 33 | 34 | Navigate to [localhost](http://localhost/) 35 | 36 | ## Overview 37 | 38 | ### Main Page 39 | 40 | Page where users can search, filter, and view invocation information 41 | 42 | ![main-page](https://user-images.githubusercontent.com/22064715/90555764-e0f01b80-e165-11ea-94df-4f6a1f2ec1f9.png) 43 | 44 | ### Top Bar 45 | 46 | ![top-bar](https://user-images.githubusercontent.com/22064715/90555769-e188b200-e165-11ea-86bf-535cc8ac4d1b.png) 47 | 48 | #### Tool Type Selection 49 | 50 | Dropdown thats allows a user to select a tool type to filter search results by. 51 | 52 | ![tool-type](https://user-images.githubusercontent.com/22064715/90555771-e2214880-e165-11ea-82f6-2a931b1a5948.png) 53 | 54 | #### Search Bar 55 | 56 | Enter queries to search for invocations. The search bar features an autocomplete typeahead for fields that can be filtered on. 57 | 58 | ![search-bar](https://user-images.githubusercontent.com/22064715/90549837-15130e80-e15d-11ea-83ae-752e68ed505b.png) 59 | 60 | #### Search Button 61 | 62 | Initiates a search for invocations based on the query in the search bar 63 | 64 | ![search-button](https://user-images.githubusercontent.com/22064715/90549687-da10db00-e15c-11ea-9a4c-f93b7b2fd071.png) 65 | 66 | #### Flaky Test Button 67 | 68 | Initiates the flaky test interface for the current query in the search bar. 69 | 70 | ![flaky-test](https://user-images.githubusercontent.com/22064715/90549943-3b38ae80-e15d-11ea-9aec-ea3b944fc180.png) 71 | 72 | #### Google Login / Logout Button 73 | 74 | Google login / logout button for authentication 75 | 76 | ![google-button](https://user-images.githubusercontent.com/22064715/90550082-720ec480-e15d-11ea-878d-8c5948708fa8.png) 77 | 78 | ### Invocation Table 79 | 80 | Table that lists the result of an invocation search 81 | 82 | #### File Modal 83 | 84 | Users can view files associated with the current invocation 85 | 86 | ![file-modal](https://user-images.githubusercontent.com/22064715/90555776-e3527580-e165-11ea-8142-81f83ca25868.png) 87 | 88 | ## Flaky Test Page 89 | 90 | Users can view the results of tests over time with different invocations. 91 | 92 | - Grey: No test was run 93 | - Green: Test Success 94 | - Red: Test Failed 95 | 96 | ![flaky-test](https://user-images.githubusercontent.com/22064715/90555782-e3eb0c00-e165-11ea-932d-010ee4b74dd6.png) 97 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/devtools/resultstore/v2/gapic/result_store_download_client_config.py: -------------------------------------------------------------------------------- 1 | config = { 2 | "interfaces": { 3 | "google.devtools.resultstore.v2.ResultStoreDownload": { 4 | "retry_codes": { 5 | "idempotent": [ 6 | "DEADLINE_EXCEEDED", 7 | "UNAVAILABLE" 8 | ], 9 | "non_idempotent": [] 10 | }, 11 | "retry_params": { 12 | "default": { 13 | "initial_retry_delay_millis": 100, 14 | "retry_delay_multiplier": 1.3, 15 | "max_retry_delay_millis": 60000, 16 | "initial_rpc_timeout_millis": 20000, 17 | "rpc_timeout_multiplier": 1.0, 18 | "max_rpc_timeout_millis": 20000, 19 | "total_timeout_millis": 600000 20 | } 21 | }, 22 | "methods": { 23 | "GetInvocation": { 24 | "timeout_millis": 60000, 25 | "retry_codes_name": "idempotent", 26 | "retry_params_name": "default" 27 | }, 28 | "SearchInvocations": { 29 | "timeout_millis": 60000, 30 | "retry_codes_name": "idempotent", 31 | "retry_params_name": "default" 32 | }, 33 | "GetInvocationDownloadMetadata": { 34 | "timeout_millis": 60000, 35 | "retry_codes_name": "idempotent", 36 | "retry_params_name": "default" 37 | }, 38 | "GetConfiguration": { 39 | "timeout_millis": 60000, 40 | "retry_codes_name": "idempotent", 41 | "retry_params_name": "default" 42 | }, 43 | "ListConfigurations": { 44 | "timeout_millis": 60000, 45 | "retry_codes_name": "idempotent", 46 | "retry_params_name": "default" 47 | }, 48 | "GetTarget": { 49 | "timeout_millis": 60000, 50 | "retry_codes_name": "idempotent", 51 | "retry_params_name": "default" 52 | }, 53 | "ListTargets": { 54 | "timeout_millis": 60000, 55 | "retry_codes_name": "idempotent", 56 | "retry_params_name": "default" 57 | }, 58 | "GetConfiguredTarget": { 59 | "timeout_millis": 60000, 60 | "retry_codes_name": "idempotent", 61 | "retry_params_name": "default" 62 | }, 63 | "ListConfiguredTargets": { 64 | "timeout_millis": 60000, 65 | "retry_codes_name": "idempotent", 66 | "retry_params_name": "default" 67 | }, 68 | "GetAction": { 69 | "timeout_millis": 60000, 70 | "retry_codes_name": "idempotent", 71 | "retry_params_name": "default" 72 | }, 73 | "ListActions": { 74 | "timeout_millis": 60000, 75 | "retry_codes_name": "idempotent", 76 | "retry_params_name": "default" 77 | }, 78 | "GetFileSet": { 79 | "timeout_millis": 60000, 80 | "retry_codes_name": "idempotent", 81 | "retry_params_name": "default" 82 | }, 83 | "ListFileSets": { 84 | "timeout_millis": 60000, 85 | "retry_codes_name": "idempotent", 86 | "retry_params_name": "default" 87 | }, 88 | "TraverseFileSets": { 89 | "timeout_millis": 60000, 90 | "retry_codes_name": "idempotent", 91 | "retry_params_name": "default" 92 | } 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/devtools/resultstore/v2/gapic/result_store_download_client_config.py: -------------------------------------------------------------------------------- 1 | config = { 2 | "interfaces": { 3 | "google.devtools.resultstore.v2.ResultStoreDownload": { 4 | "retry_codes": { 5 | "idempotent": [ 6 | "DEADLINE_EXCEEDED", 7 | "UNAVAILABLE" 8 | ], 9 | "non_idempotent": [] 10 | }, 11 | "retry_params": { 12 | "default": { 13 | "initial_retry_delay_millis": 100, 14 | "retry_delay_multiplier": 1.3, 15 | "max_retry_delay_millis": 60000, 16 | "initial_rpc_timeout_millis": 20000, 17 | "rpc_timeout_multiplier": 1.0, 18 | "max_rpc_timeout_millis": 20000, 19 | "total_timeout_millis": 600000 20 | } 21 | }, 22 | "methods": { 23 | "GetInvocation": { 24 | "timeout_millis": 60000, 25 | "retry_codes_name": "idempotent", 26 | "retry_params_name": "default" 27 | }, 28 | "SearchInvocations": { 29 | "timeout_millis": 60000, 30 | "retry_codes_name": "idempotent", 31 | "retry_params_name": "default" 32 | }, 33 | "GetInvocationDownloadMetadata": { 34 | "timeout_millis": 60000, 35 | "retry_codes_name": "idempotent", 36 | "retry_params_name": "default" 37 | }, 38 | "GetConfiguration": { 39 | "timeout_millis": 60000, 40 | "retry_codes_name": "idempotent", 41 | "retry_params_name": "default" 42 | }, 43 | "ListConfigurations": { 44 | "timeout_millis": 60000, 45 | "retry_codes_name": "idempotent", 46 | "retry_params_name": "default" 47 | }, 48 | "GetTarget": { 49 | "timeout_millis": 60000, 50 | "retry_codes_name": "idempotent", 51 | "retry_params_name": "default" 52 | }, 53 | "ListTargets": { 54 | "timeout_millis": 60000, 55 | "retry_codes_name": "idempotent", 56 | "retry_params_name": "default" 57 | }, 58 | "GetConfiguredTarget": { 59 | "timeout_millis": 60000, 60 | "retry_codes_name": "idempotent", 61 | "retry_params_name": "default" 62 | }, 63 | "ListConfiguredTargets": { 64 | "timeout_millis": 60000, 65 | "retry_codes_name": "idempotent", 66 | "retry_params_name": "default" 67 | }, 68 | "GetAction": { 69 | "timeout_millis": 60000, 70 | "retry_codes_name": "idempotent", 71 | "retry_params_name": "default" 72 | }, 73 | "ListActions": { 74 | "timeout_millis": 60000, 75 | "retry_codes_name": "idempotent", 76 | "retry_params_name": "default" 77 | }, 78 | "GetFileSet": { 79 | "timeout_millis": 60000, 80 | "retry_codes_name": "idempotent", 81 | "retry_params_name": "default" 82 | }, 83 | "ListFileSets": { 84 | "timeout_millis": 60000, 85 | "retry_codes_name": "idempotent", 86 | "retry_params_name": "default" 87 | }, 88 | "TraverseFileSets": { 89 | "timeout_millis": 60000, 90 | "retry_codes_name": "idempotent", 91 | "retry_params_name": "default" 92 | } 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /docs/code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Google Open Source Community Guidelines 2 | 3 | At Google, we recognize and celebrate the creativity and collaboration of open 4 | source contributors and the diversity of skills, experiences, cultures, and 5 | opinions they bring to the projects and communities they participate in. 6 | 7 | Every one of Google's open source projects and communities are inclusive 8 | environments, based on treating all individuals respectfully, regardless of 9 | gender identity and expression, sexual orientation, disabilities, 10 | neurodiversity, physical appearance, body size, ethnicity, nationality, race, 11 | age, religion, or similar personal characteristic. 12 | 13 | We value diverse opinions, but we value respectful behavior more. 14 | 15 | Respectful behavior includes: 16 | 17 | * Being considerate, kind, constructive, and helpful. 18 | * Not engaging in demeaning, discriminatory, harassing, hateful, sexualized, or 19 | physically threatening behavior, speech, and imagery. 20 | * Not engaging in unwanted physical contact. 21 | 22 | Some Google open source projects [may adopt][] an explicit project code of 23 | conduct, which may have additional detailed expectations for participants. Most 24 | of those projects will use our [modified Contributor Covenant][]. 25 | 26 | [may adopt]: https://opensource.google/docs/releasing/preparing/#conduct 27 | [modified Contributor Covenant]: https://opensource.google/docs/releasing/template/CODE_OF_CONDUCT/ 28 | 29 | ## Resolve peacefully 30 | 31 | We do not believe that all conflict is necessarily bad; healthy debate and 32 | disagreement often yields positive results. However, it is never okay to be 33 | disrespectful. 34 | 35 | If you see someone behaving disrespectfully, you are encouraged to address the 36 | behavior directly with those involved. Many issues can be resolved quickly and 37 | easily, and this gives people more control over the outcome of their dispute. 38 | If you are unable to resolve the matter for any reason, or if the behavior is 39 | threatening or harassing, report it. We are dedicated to providing an 40 | environment where participants feel welcome and safe. 41 | 42 | ## Reporting problems 43 | 44 | Some Google open source projects may adopt a project-specific code of conduct. 45 | In those cases, a Google employee will be identified as the Project Steward, 46 | who will receive and handle reports of code of conduct violations. In the event 47 | that a project hasn’t identified a Project Steward, you can report problems by 48 | emailing opensource@google.com. 49 | 50 | We will investigate every complaint, but you may not receive a direct response. 51 | We will use our discretion in determining when and how to follow up on reported 52 | incidents, which may range from not taking action to permanent expulsion from 53 | the project and project-sponsored spaces. We will notify the accused of the 54 | report and provide them an opportunity to discuss it before any action is 55 | taken. The identity of the reporter will be omitted from the details of the 56 | report supplied to the accused. In potentially harmful situations, such as 57 | ongoing harassment or threats to anyone's safety, we may take action without 58 | notice. 59 | 60 | *This document was adapted from the [IndieWeb Code of Conduct][] and can also 61 | be found at .* 62 | 63 | [IndieWeb Code of Conduct]: https://indieweb.org/code-of-conduct 64 | -------------------------------------------------------------------------------- /resultstoresearch/resultstoresearch/v1/file.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package resultstoresearch.v1; 4 | 5 | import "wrappers.proto"; 6 | 7 | // The metadata for a file or an archive file entry. 8 | message File { 9 | // If known, the hash function used to compute this digest. 10 | enum HashType { 11 | // Unknown 12 | HASH_TYPE_UNSPECIFIED = 0; 13 | 14 | // MD5 15 | MD5 = 1; 16 | 17 | // SHA-1 18 | SHA1 = 2; 19 | 20 | // SHA-256 21 | SHA256 = 3; 22 | } 23 | 24 | // The identifier of the file or archive entry. 25 | // User-provided, must be unique for the repeated field it is in. When an 26 | // Append RPC is called with a Files field populated, if a File already exists 27 | // with this ID, that File will be overwritten with the new File proto. 28 | string uid = 1; 29 | 30 | // The URI of a file. 31 | // This could also be the URI of an entire archive. 32 | // Most log data doesn't need to be stored forever, so a ttl is suggested. 33 | // Note that if you ever move or delete the file at this URI, the link from 34 | // the server will be broken. 35 | string uri = 2; 36 | 37 | // (Optional) The length of the file in bytes. Allows the filesize to be 38 | // shown in the UI. Omit if file is still being written or length is 39 | // not known. This could also be the length of an entire archive. 40 | Int64Value length = 3; 41 | 42 | // (Optional) The content-type (aka MIME-type) of the file. This is sent to 43 | // the web browser so it knows how to handle the file. (e.g. text/plain, 44 | // image/jpeg, text/html, etc). For zip archives, use "application/zip". 45 | string content_type = 4; 46 | 47 | // (Optional) If the above path, length, and content_type are referring to an 48 | // archive, and you wish to refer to a particular entry within that archive, 49 | // put the particular archive entry data here. 50 | ArchiveEntry archive_entry = 5; 51 | 52 | // (Optional) A url to a content display app/site for this file or archive 53 | // entry. 54 | string content_viewer = 6; 55 | 56 | // (Optional) Whether to hide this file or archive entry in the UI. Defaults 57 | // to false. A checkbox lets users see hidden files, but they're hidden by 58 | // default. 59 | bool hidden = 7; 60 | 61 | // (Optional) A short description of what this file or archive entry 62 | // contains. This description should help someone viewing the list of these 63 | // files to understand the purpose of this file and what they would want to 64 | // view it for. 65 | string description = 8; 66 | 67 | // (Optional) digest of this file in hexadecimal-like string if known. 68 | string digest = 9; 69 | 70 | // (Optional) The algorithm corresponding to the digest if known. 71 | HashType hash_type = 10; 72 | } 73 | 74 | // Information specific to an entry in an archive. 75 | message ArchiveEntry { 76 | // The relative path of the entry within the archive. 77 | string path = 1; 78 | 79 | // (Optional) The uncompressed length of the archive entry in bytes. Allows 80 | // the entry size to be shown in the UI. Omit if the length is not known. 81 | Int64Value length = 2; 82 | 83 | // (Optional) The content-type (aka MIME-type) of the archive entry. (e.g. 84 | // text/plain, image/jpeg, text/html, etc). This is sent to the web browser 85 | // so it knows how to handle the entry. 86 | string content_type = 3; 87 | } 88 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreui.py: -------------------------------------------------------------------------------- 1 | from base64 import b64encode 2 | from absl import (flags, app) 3 | from resultstore_client import ResultStoreClient 4 | from credentials import Credentials 5 | from resultstoreapi.cloud.devtools.resultstore_v2.proto import common_pb2 6 | 7 | FLAGS = flags.FLAGS 8 | 9 | 10 | def initialize_flags(): 11 | flags.DEFINE_string( 12 | 'command', '', 13 | 'Available commands to execute: get-invocation, create-invocation, single-upload' 14 | ) 15 | flags.DEFINE_string('config_id', 'default', 'Config Id') 16 | flags.DEFINE_string('target_name', '', 'Target Name') 17 | flags.DEFINE_string('invocation_id', '', 'Invocation ID') 18 | flags.DEFINE_string('authorization_token', '', 'Authorization Token') 19 | flags.DEFINE_string('action_type', 'TEST', 'Action Type') 20 | flags.DEFINE_string('invocation_name', '', 'Invocation Name') 21 | flags.DEFINE_string('bigstore_project_name', 22 | 'google.com:gchips-productivity', 23 | 'The project name for the bigstore client') 24 | flags.DEFINE_enum('status', 'PASSED', common_pb2.Status.keys(), 25 | 'Status of this action (if command not provided)') 26 | flags.DEFINE_string('bucket_name', 'sival-logs', 27 | 'Google Cloud Storage Bucket Name') 28 | flags.DEFINE_list('files', [], 'files to be uploaded with the action') 29 | flags.DEFINE_string( 30 | 'channel_target', 'resultstore.googleapis.com', 31 | 'The host and port of the service that the channel is created to') 32 | flags.DEFINE_bool( 33 | 'create_config', True, 34 | 'Boolean to control creation of configuration during single or batch upload' 35 | ) 36 | flags.DEFINE_string('resume_token', '', 37 | 'Current resume token for batch uplaods') 38 | flags.DEFINE_string('next_resume_token', '', 39 | 'Next resume token for batch uploads') 40 | 41 | 42 | def main(argv): 43 | resultstore_creds = Credentials() 44 | resultstore_client = ResultStoreClient(resultstore_creds, FLAGS) 45 | resume_token = b64encode(bytes(FLAGS.resume_token, 'utf-8')) 46 | next_resume_token = b64encode(bytes(FLAGS.next_resume_token, 'utf-8')) 47 | with resultstore_creds.create_secure_channel( 48 | FLAGS.channel_target) as channel: 49 | if not FLAGS.command: 50 | raise Exception( 51 | 'No command specified! Please specific a command with --command' 52 | ) 53 | elif FLAGS.command == 'get-invocation': 54 | resultstore_client.get_invocation(FLAGS.invocation_name) 55 | elif FLAGS.command == 'create-invocation': 56 | resultstore_client.create_invocation() 57 | elif FLAGS.command == 'single-upload': 58 | resultstore_client.single_upload() 59 | elif FLAGS.command == 'batch-upload': 60 | resultstore_client.batch_upload_wrapper(resume_token, 61 | next_resume_token) 62 | elif FLAGS.command == 'finalize-batch-upload': 63 | resultstore_client.finalize_batch_upload(resume_token, 64 | next_resume_token, 65 | FLAGS.invocation_id) 66 | else: 67 | raise Exception('Command not found: {}'.format(FLAGS.command)) 68 | 69 | 70 | if __name__ == '__main__': 71 | initialize_flags() 72 | app.run(main) 73 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/devtools/resultstore/v2/types.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright 2020 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 | # https://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 | 18 | from __future__ import absolute_import 19 | import sys 20 | 21 | from google.api_core.protobuf_helpers import get_messages 22 | 23 | from google.cloud.devtools.resultstore.v2.proto import action_pb2 24 | from google.cloud.devtools.resultstore.v2.proto import common_pb2 25 | from google.cloud.devtools.resultstore.v2.proto import configuration_pb2 26 | from google.cloud.devtools.resultstore.v2.proto import configured_target_pb2 27 | from google.cloud.devtools.resultstore.v2.proto import coverage_pb2 28 | from google.cloud.devtools.resultstore.v2.proto import coverage_summary_pb2 29 | from google.cloud.devtools.resultstore.v2.proto import download_metadata_pb2 30 | from google.cloud.devtools.resultstore.v2.proto import file_pb2 31 | from google.cloud.devtools.resultstore.v2.proto import file_processing_error_pb2 32 | from google.cloud.devtools.resultstore.v2.proto import file_set_pb2 33 | from google.cloud.devtools.resultstore.v2.proto import invocation_pb2 34 | from google.cloud.devtools.resultstore.v2.proto import resultstore_download_pb2 35 | from google.cloud.devtools.resultstore.v2.proto import resultstore_file_download_pb2 36 | from google.cloud.devtools.resultstore.v2.proto import resultstore_upload_pb2 37 | from google.cloud.devtools.resultstore.v2.proto import target_pb2 38 | from google.cloud.devtools.resultstore.v2.proto import test_suite_pb2 39 | from google.cloud.devtools.resultstore.v2.proto import upload_metadata_pb2 40 | from google.protobuf import duration_pb2 41 | from google.protobuf import empty_pb2 42 | from google.protobuf import field_mask_pb2 43 | from google.protobuf import timestamp_pb2 44 | from google.protobuf import wrappers_pb2 45 | 46 | 47 | _shared_modules = [ 48 | duration_pb2, 49 | empty_pb2, 50 | field_mask_pb2, 51 | timestamp_pb2, 52 | wrappers_pb2, 53 | ] 54 | 55 | _local_modules = [ 56 | action_pb2, 57 | common_pb2, 58 | configuration_pb2, 59 | configured_target_pb2, 60 | coverage_pb2, 61 | coverage_summary_pb2, 62 | download_metadata_pb2, 63 | file_pb2, 64 | file_processing_error_pb2, 65 | file_set_pb2, 66 | invocation_pb2, 67 | resultstore_download_pb2, 68 | resultstore_file_download_pb2, 69 | resultstore_upload_pb2, 70 | target_pb2, 71 | test_suite_pb2, 72 | upload_metadata_pb2, 73 | ] 74 | 75 | names = [] 76 | 77 | for module in _shared_modules: # pragma: NO COVER 78 | for name, message in get_messages(module).items(): 79 | setattr(sys.modules[__name__], name, message) 80 | names.append(name) 81 | for module in _local_modules: 82 | for name, message in get_messages(module).items(): 83 | message.__module__ = 'google.cloud.devtools.resultstore.v2.types' 84 | setattr(sys.modules[__name__], name, message) 85 | names.append(name) 86 | 87 | 88 | __all__ = tuple(sorted(names)) 89 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/devtools/resultstore/v2/types.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright 2020 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 | # https://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 | 18 | from __future__ import absolute_import 19 | import sys 20 | 21 | from google.api_core.protobuf_helpers import get_messages 22 | 23 | from google.cloud.devtools.resultstore.v2.proto import action_pb2 24 | from google.cloud.devtools.resultstore.v2.proto import common_pb2 25 | from google.cloud.devtools.resultstore.v2.proto import configuration_pb2 26 | from google.cloud.devtools.resultstore.v2.proto import configured_target_pb2 27 | from google.cloud.devtools.resultstore.v2.proto import coverage_pb2 28 | from google.cloud.devtools.resultstore.v2.proto import coverage_summary_pb2 29 | from google.cloud.devtools.resultstore.v2.proto import download_metadata_pb2 30 | from google.cloud.devtools.resultstore.v2.proto import file_pb2 31 | from google.cloud.devtools.resultstore.v2.proto import file_processing_error_pb2 32 | from google.cloud.devtools.resultstore.v2.proto import file_set_pb2 33 | from google.cloud.devtools.resultstore.v2.proto import invocation_pb2 34 | from google.cloud.devtools.resultstore.v2.proto import resultstore_download_pb2 35 | from google.cloud.devtools.resultstore.v2.proto import resultstore_file_download_pb2 36 | from google.cloud.devtools.resultstore.v2.proto import resultstore_upload_pb2 37 | from google.cloud.devtools.resultstore.v2.proto import target_pb2 38 | from google.cloud.devtools.resultstore.v2.proto import test_suite_pb2 39 | from google.cloud.devtools.resultstore.v2.proto import upload_metadata_pb2 40 | from google.protobuf import duration_pb2 41 | from google.protobuf import empty_pb2 42 | from google.protobuf import field_mask_pb2 43 | from google.protobuf import timestamp_pb2 44 | from google.protobuf import wrappers_pb2 45 | 46 | 47 | _shared_modules = [ 48 | duration_pb2, 49 | empty_pb2, 50 | field_mask_pb2, 51 | timestamp_pb2, 52 | wrappers_pb2, 53 | ] 54 | 55 | _local_modules = [ 56 | action_pb2, 57 | common_pb2, 58 | configuration_pb2, 59 | configured_target_pb2, 60 | coverage_pb2, 61 | coverage_summary_pb2, 62 | download_metadata_pb2, 63 | file_pb2, 64 | file_processing_error_pb2, 65 | file_set_pb2, 66 | invocation_pb2, 67 | resultstore_download_pb2, 68 | resultstore_file_download_pb2, 69 | resultstore_upload_pb2, 70 | target_pb2, 71 | test_suite_pb2, 72 | upload_metadata_pb2, 73 | ] 74 | 75 | names = [] 76 | 77 | for module in _shared_modules: # pragma: NO COVER 78 | for name, message in get_messages(module).items(): 79 | setattr(sys.modules[__name__], name, message) 80 | names.append(name) 81 | for module in _local_modules: 82 | for name, message in get_messages(module).items(): 83 | message.__module__ = 'google.cloud.devtools.resultstore.v2.types' 84 | setattr(sys.modules[__name__], name, message) 85 | names.append(name) 86 | 87 | 88 | __all__ = tuple(sorted(names)) 89 | -------------------------------------------------------------------------------- /resultstoresearch/client/src/api/coverage_summary_pb.d.ts: -------------------------------------------------------------------------------- 1 | import * as jspb from "google-protobuf" 2 | 3 | import * as common_pb from './common_pb'; 4 | 5 | export class LineCoverageSummary extends jspb.Message { 6 | getInstrumentedLineCount(): number; 7 | setInstrumentedLineCount(value: number): LineCoverageSummary; 8 | 9 | getExecutedLineCount(): number; 10 | setExecutedLineCount(value: number): LineCoverageSummary; 11 | 12 | serializeBinary(): Uint8Array; 13 | toObject(includeInstance?: boolean): LineCoverageSummary.AsObject; 14 | static toObject(includeInstance: boolean, msg: LineCoverageSummary): LineCoverageSummary.AsObject; 15 | static serializeBinaryToWriter(message: LineCoverageSummary, writer: jspb.BinaryWriter): void; 16 | static deserializeBinary(bytes: Uint8Array): LineCoverageSummary; 17 | static deserializeBinaryFromReader(message: LineCoverageSummary, reader: jspb.BinaryReader): LineCoverageSummary; 18 | } 19 | 20 | export namespace LineCoverageSummary { 21 | export type AsObject = { 22 | instrumentedLineCount: number, 23 | executedLineCount: number, 24 | } 25 | } 26 | 27 | export class BranchCoverageSummary extends jspb.Message { 28 | getTotalBranchCount(): number; 29 | setTotalBranchCount(value: number): BranchCoverageSummary; 30 | 31 | getExecutedBranchCount(): number; 32 | setExecutedBranchCount(value: number): BranchCoverageSummary; 33 | 34 | getTakenBranchCount(): number; 35 | setTakenBranchCount(value: number): BranchCoverageSummary; 36 | 37 | serializeBinary(): Uint8Array; 38 | toObject(includeInstance?: boolean): BranchCoverageSummary.AsObject; 39 | static toObject(includeInstance: boolean, msg: BranchCoverageSummary): BranchCoverageSummary.AsObject; 40 | static serializeBinaryToWriter(message: BranchCoverageSummary, writer: jspb.BinaryWriter): void; 41 | static deserializeBinary(bytes: Uint8Array): BranchCoverageSummary; 42 | static deserializeBinaryFromReader(message: BranchCoverageSummary, reader: jspb.BinaryReader): BranchCoverageSummary; 43 | } 44 | 45 | export namespace BranchCoverageSummary { 46 | export type AsObject = { 47 | totalBranchCount: number, 48 | executedBranchCount: number, 49 | takenBranchCount: number, 50 | } 51 | } 52 | 53 | export class LanguageCoverageSummary extends jspb.Message { 54 | getLanguage(): common_pb.Language; 55 | setLanguage(value: common_pb.Language): LanguageCoverageSummary; 56 | 57 | getLineSummary(): LineCoverageSummary | undefined; 58 | setLineSummary(value?: LineCoverageSummary): LanguageCoverageSummary; 59 | hasLineSummary(): boolean; 60 | clearLineSummary(): LanguageCoverageSummary; 61 | 62 | getBranchSummary(): BranchCoverageSummary | undefined; 63 | setBranchSummary(value?: BranchCoverageSummary): LanguageCoverageSummary; 64 | hasBranchSummary(): boolean; 65 | clearBranchSummary(): LanguageCoverageSummary; 66 | 67 | serializeBinary(): Uint8Array; 68 | toObject(includeInstance?: boolean): LanguageCoverageSummary.AsObject; 69 | static toObject(includeInstance: boolean, msg: LanguageCoverageSummary): LanguageCoverageSummary.AsObject; 70 | static serializeBinaryToWriter(message: LanguageCoverageSummary, writer: jspb.BinaryWriter): void; 71 | static deserializeBinary(bytes: Uint8Array): LanguageCoverageSummary; 72 | static deserializeBinaryFromReader(message: LanguageCoverageSummary, reader: jspb.BinaryReader): LanguageCoverageSummary; 73 | } 74 | 75 | export namespace LanguageCoverageSummary { 76 | export type AsObject = { 77 | language: common_pb.Language, 78 | lineSummary?: LineCoverageSummary.AsObject, 79 | branchSummary?: BranchCoverageSummary.AsObject, 80 | } 81 | } 82 | 83 | -------------------------------------------------------------------------------- /resultstoreui/resultstore_test_utils.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import MagicMock 2 | from resultstore_client import ResultStoreClient 3 | from resultstoreapi.cloud.devtools.resultstore_v2.proto import ( 4 | resultstore_download_pb2_grpc, resultstore_download_pb2, invocation_pb2, 5 | configuration_pb2, target_pb2, configured_target_pb2, action_pb2) 6 | 7 | 8 | def create_return_invocation(request): 9 | return invocation_pb2.Invocation( 10 | id={'invocation_id': request.invocation_id}) 11 | 12 | 13 | def create_return_target(request): 14 | return target_pb2.Target(name="invocations/{}/targets/{}".format( 15 | request.target.id.invocation_id, request.target.id.target_id), 16 | id={'target_id': request.target.id.target_id}) 17 | 18 | 19 | def create_return_configuration(request): 20 | return configuration_pb2.Configuration( 21 | name="invocations/{}/configs/{}".format( 22 | request.configuration.id.invocation_id, 23 | request.configuration.id.configuration_id), 24 | id={'configuration_id': request.configuration.id.configuration_id}) 25 | 26 | 27 | def create_return_configured_target(request): 28 | return configured_target_pb2.ConfiguredTarget( 29 | name="invocations/{}/targets/{}/configuredTargets/{}".format( 30 | request.configured_target.id.invocation_id, 31 | request.configured_target.id.target_id, 32 | request.configured_target.id.configuration_id), 33 | id={ 34 | 'configuration_id': request.configured_target.id.configuration_id, 35 | 'target_id': request.configured_target.id.target_id, 36 | 'invocation_id': request.configured_target.id.invocation_id 37 | }) 38 | 39 | 40 | def create_return_action(request): 41 | return action_pb2.Action( 42 | name="invocations/{}/targets/{}/configuredTargets/{}/actions/{}". 43 | format(request.action.id.invocation_id, request.action.id.target_id, 44 | request.action.id.configuration_id, 45 | request.action.id.action_id), 46 | id={ 47 | 'configuration_id': request.action.id.configuration_id, 48 | 'target_id': request.action.id.target_id, 49 | 'invocation_id': request.action.id.invocation_id, 50 | 'action_id': request.action.id.action_id 51 | }) 52 | 53 | 54 | def create_finalized_configured_target(request): 55 | split_name = request.name.split('/') 56 | return configured_target_pb2.ConfiguredTarget(name=request.name, 57 | id={ 58 | "invocation_id": 59 | split_name[1], 60 | "target_id": 61 | split_name[3], 62 | "configuration_id": 63 | split_name[5] 64 | }) 65 | 66 | 67 | def create_finalized_target(request): 68 | split_name = request.name.split('/') 69 | return target_pb2.Target(name=request.name, 70 | id={ 71 | "invocation_id": split_name[1], 72 | "target_id": split_name[3], 73 | }) 74 | 75 | 76 | def initialize_client(flags, credentials=MagicMock()): 77 | flags.authorization_token = '' 78 | credentials.get_active_channel = MagicMock(return_value=None) 79 | return ResultStoreClient(credentials, flags) 80 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/devtools/resultstore_v2/proto/resultstore_file_download_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | from resultstoreapi.cloud.devtools.resultstore_v2.proto import resultstore_file_download_pb2 as google_dot_cloud_dot_devtools_dot_resultstore__v2_dot_proto_dot_resultstore__file__download__pb2 5 | 6 | 7 | class ResultStoreFileDownloadStub(object): 8 | """This API allows download of File messages referenced in 9 | ResultStore resources. 10 | """ 11 | 12 | def __init__(self, channel): 13 | """Constructor. 14 | 15 | Args: 16 | channel: A grpc.Channel. 17 | """ 18 | self.GetFile = channel.unary_stream( 19 | '/google.devtools.resultstore.v2.ResultStoreFileDownload/GetFile', 20 | request_serializer=google_dot_cloud_dot_devtools_dot_resultstore__v2_dot_proto_dot_resultstore__file__download__pb2.GetFileRequest.SerializeToString, 21 | response_deserializer=google_dot_cloud_dot_devtools_dot_resultstore__v2_dot_proto_dot_resultstore__file__download__pb2.GetFileResponse.FromString, 22 | ) 23 | self.GetFileTail = channel.unary_unary( 24 | '/google.devtools.resultstore.v2.ResultStoreFileDownload/GetFileTail', 25 | request_serializer=google_dot_cloud_dot_devtools_dot_resultstore__v2_dot_proto_dot_resultstore__file__download__pb2.GetFileTailRequest.SerializeToString, 26 | response_deserializer=google_dot_cloud_dot_devtools_dot_resultstore__v2_dot_proto_dot_resultstore__file__download__pb2.GetFileTailResponse.FromString, 27 | ) 28 | 29 | 30 | class ResultStoreFileDownloadServicer(object): 31 | """This API allows download of File messages referenced in 32 | ResultStore resources. 33 | """ 34 | 35 | def GetFile(self, request, context): 36 | """Retrieves the File with the given uri. 37 | returns a stream of bytes to be stitched together in order. 38 | 39 | An error will be reported in the following cases: 40 | - If the File is not found. 41 | - If the given File uri is badly formatted. 42 | """ 43 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 44 | context.set_details('Method not implemented!') 45 | raise NotImplementedError('Method not implemented!') 46 | 47 | def GetFileTail(self, request, context): 48 | """Retrieves the tail of a File with the given uri. 49 | 50 | An error will be reported in the following cases: 51 | - If the File is not found. 52 | - If the given File uri is badly formatted. 53 | """ 54 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 55 | context.set_details('Method not implemented!') 56 | raise NotImplementedError('Method not implemented!') 57 | 58 | 59 | def add_ResultStoreFileDownloadServicer_to_server(servicer, server): 60 | rpc_method_handlers = { 61 | 'GetFile': grpc.unary_stream_rpc_method_handler( 62 | servicer.GetFile, 63 | request_deserializer=google_dot_cloud_dot_devtools_dot_resultstore__v2_dot_proto_dot_resultstore__file__download__pb2.GetFileRequest.FromString, 64 | response_serializer=google_dot_cloud_dot_devtools_dot_resultstore__v2_dot_proto_dot_resultstore__file__download__pb2.GetFileResponse.SerializeToString, 65 | ), 66 | 'GetFileTail': grpc.unary_unary_rpc_method_handler( 67 | servicer.GetFileTail, 68 | request_deserializer=google_dot_cloud_dot_devtools_dot_resultstore__v2_dot_proto_dot_resultstore__file__download__pb2.GetFileTailRequest.FromString, 69 | response_serializer=google_dot_cloud_dot_devtools_dot_resultstore__v2_dot_proto_dot_resultstore__file__download__pb2.GetFileTailResponse.SerializeToString, 70 | ), 71 | } 72 | generic_handler = grpc.method_handlers_generic_handler( 73 | 'google.devtools.resultstore.v2.ResultStoreFileDownload', rpc_method_handlers) 74 | server.add_generic_rpc_handlers((generic_handler,)) 75 | -------------------------------------------------------------------------------- /resultstoresearch/resultstoresearch/v1/coverage.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package resultstoresearch.v1; 4 | 5 | // Describes line coverage for a file 6 | message LineCoverage { 7 | // Which source lines in the file represent the start of a statement that was 8 | // instrumented to detect whether it was executed by the test. 9 | // 10 | // This is a bitfield where i-th bit corresponds to the i-th line. Divide line 11 | // number by 8 to get index into byte array. Mod line number by 8 to get bit 12 | // number (0 = LSB, 7 = MSB). 13 | // 14 | // A 1 denotes the line was instrumented. 15 | // A 0 denotes the line was not instrumented. 16 | bytes instrumented_lines = 1; 17 | 18 | // Which of the instrumented source lines were executed by the test. Should 19 | // include lines that were not instrumented. 20 | // 21 | // This is a bitfield where i-th bit corresponds to the i-th line. Divide line 22 | // number by 8 to get index into byte array. Mod line number by 8 to get bit 23 | // number (0 = LSB, 7 = MSB). 24 | // 25 | // A 1 denotes the line was executed. 26 | // A 0 denotes the line was not executed. 27 | bytes executed_lines = 2; 28 | } 29 | 30 | // Describes branch coverage for a file 31 | message BranchCoverage { 32 | // The field branch_present denotes the lines containing at least one branch. 33 | // 34 | // This is a bitfield where i-th bit corresponds to the i-th line. Divide line 35 | // number by 8 to get index into byte array. Mod line number by 8 to get bit 36 | // number (0 = LSB, 7 = MSB). 37 | // 38 | // A 1 denotes the line contains at least one branch. 39 | // A 0 denotes the line contains no branches. 40 | bytes branch_present = 1; 41 | 42 | // Contains the number of branches present, only for the lines which have the 43 | // corresponding bit set in branch_present, in a relative order ignoring 44 | // lines which do not have any branches. 45 | repeated int32 branches_in_line = 2; 46 | 47 | // As each branch can have any one of the following three states: not 48 | // executed, executed but not taken, executed and taken. 49 | // 50 | // This is a bitfield where i-th bit corresponds to the i-th branch. Divide 51 | // branch number by 8 to get index into byte array. Mod branch number by 8 to 52 | // get bit number (0 = LSB, 7 = MSB). 53 | // 54 | // i-th bit of the following two byte arrays are used to denote the above 55 | // mentioned states. 56 | // 57 | // not executed: i-th bit of executed == 0 && i-th bit of taken == 0 58 | // executed but not taken: i-th bit of executed == 1 && i-th bit of taken == 0 59 | // executed and taken: i-th bit of executed == 1 && i-th bit of taken == 1 60 | bytes executed = 3; 61 | 62 | // Described above. 63 | bytes taken = 4; 64 | } 65 | 66 | // Describes code coverage for a particular file under test. 67 | message FileCoverage { 68 | // Path of source file within the SourceContext of this Invocation. 69 | string path = 1; 70 | 71 | // Details of lines in a file required to calculate line coverage. 72 | LineCoverage line_coverage = 2; 73 | 74 | // Details of branches in a file required to calculate branch coverage. 75 | BranchCoverage branch_coverage = 3; 76 | } 77 | 78 | // Describes code coverage for a build or test Action. This is used to store 79 | // baseline coverage for build Actions and test coverage for test Actions. 80 | message ActionCoverage { 81 | // List of coverage info for all source files that the TestResult covers. 82 | repeated FileCoverage file_coverages = 2; 83 | } 84 | 85 | // Describes aggregate code coverage for a collection of build or test Actions. 86 | // A line or branch is covered if and only if it is covered in any of the build 87 | // or test actions. 88 | message AggregateCoverage { 89 | // Aggregated coverage info for all source files that the actions cover. 90 | repeated FileCoverage file_coverages = 1; 91 | } 92 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/devtools/resultstore_v2/proto/resultstore_file_download_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | import grpc 3 | 4 | from resultstoreapi.cloud.devtools.resultstore_v2.proto import resultstore_file_download_pb2 as google_dot_cloud_dot_devtools_dot_resultstore__v2_dot_proto_dot_resultstore__file__download__pb2 5 | 6 | 7 | class ResultStoreFileDownloadStub(object): 8 | """This API allows download of File messages referenced in 9 | ResultStore resources. 10 | """ 11 | 12 | def __init__(self, channel): 13 | """Constructor. 14 | 15 | Args: 16 | channel: A grpc.Channel. 17 | """ 18 | self.GetFile = channel.unary_stream( 19 | '/google.devtools.resultstore.v2.ResultStoreFileDownload/GetFile', 20 | request_serializer=google_dot_cloud_dot_devtools_dot_resultstore__v2_dot_proto_dot_resultstore__file__download__pb2.GetFileRequest.SerializeToString, 21 | response_deserializer=google_dot_cloud_dot_devtools_dot_resultstore__v2_dot_proto_dot_resultstore__file__download__pb2.GetFileResponse.FromString, 22 | ) 23 | self.GetFileTail = channel.unary_unary( 24 | '/google.devtools.resultstore.v2.ResultStoreFileDownload/GetFileTail', 25 | request_serializer=google_dot_cloud_dot_devtools_dot_resultstore__v2_dot_proto_dot_resultstore__file__download__pb2.GetFileTailRequest.SerializeToString, 26 | response_deserializer=google_dot_cloud_dot_devtools_dot_resultstore__v2_dot_proto_dot_resultstore__file__download__pb2.GetFileTailResponse.FromString, 27 | ) 28 | 29 | 30 | class ResultStoreFileDownloadServicer(object): 31 | """This API allows download of File messages referenced in 32 | ResultStore resources. 33 | """ 34 | 35 | def GetFile(self, request, context): 36 | """Retrieves the File with the given uri. 37 | returns a stream of bytes to be stitched together in order. 38 | 39 | An error will be reported in the following cases: 40 | - If the File is not found. 41 | - If the given File uri is badly formatted. 42 | """ 43 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 44 | context.set_details('Method not implemented!') 45 | raise NotImplementedError('Method not implemented!') 46 | 47 | def GetFileTail(self, request, context): 48 | """Retrieves the tail of a File with the given uri. 49 | 50 | An error will be reported in the following cases: 51 | - If the File is not found. 52 | - If the given File uri is badly formatted. 53 | """ 54 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 55 | context.set_details('Method not implemented!') 56 | raise NotImplementedError('Method not implemented!') 57 | 58 | 59 | def add_ResultStoreFileDownloadServicer_to_server(servicer, server): 60 | rpc_method_handlers = { 61 | 'GetFile': grpc.unary_stream_rpc_method_handler( 62 | servicer.GetFile, 63 | request_deserializer=google_dot_cloud_dot_devtools_dot_resultstore__v2_dot_proto_dot_resultstore__file__download__pb2.GetFileRequest.FromString, 64 | response_serializer=google_dot_cloud_dot_devtools_dot_resultstore__v2_dot_proto_dot_resultstore__file__download__pb2.GetFileResponse.SerializeToString, 65 | ), 66 | 'GetFileTail': grpc.unary_unary_rpc_method_handler( 67 | servicer.GetFileTail, 68 | request_deserializer=google_dot_cloud_dot_devtools_dot_resultstore__v2_dot_proto_dot_resultstore__file__download__pb2.GetFileTailRequest.FromString, 69 | response_serializer=google_dot_cloud_dot_devtools_dot_resultstore__v2_dot_proto_dot_resultstore__file__download__pb2.GetFileTailResponse.SerializeToString, 70 | ), 71 | } 72 | generic_handler = grpc.method_handlers_generic_handler( 73 | 'google.devtools.resultstore.v2.ResultStoreFileDownload', rpc_method_handlers) 74 | server.add_generic_rpc_handlers((generic_handler,)) 75 | -------------------------------------------------------------------------------- /gRPC-build/BUILD.bazel: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by BuildFileGenerator 2 | 3 | # This is an API workspace, having public visibility by default makes perfect sense. 4 | package(default_visibility = ["//visibility:public"]) 5 | 6 | ############################################################################## 7 | # Common 8 | ############################################################################## 9 | load("@rules_proto//proto:defs.bzl", "proto_library") 10 | load("@com_google_googleapis_imports//:imports.bzl", "proto_library_with_info") 11 | 12 | proto_library( 13 | name = "resultstore_proto", 14 | srcs = [ 15 | "action.proto", 16 | "common.proto", 17 | "configuration.proto", 18 | "configured_target.proto", 19 | "coverage_summary.proto", 20 | "coverage.proto", 21 | "download_metadata.proto", 22 | "file_processing_error.proto", 23 | "file_set.proto", 24 | "file.proto", 25 | "invocation.proto", 26 | "resultstore_download.proto", 27 | "resultstore_file_download.proto", 28 | "resultstore_upload.proto", 29 | "target.proto", 30 | "test_suite.proto", 31 | "upload_metadata.proto" 32 | ], 33 | deps = [ 34 | "//google/api:annotations_proto", 35 | "//google/api:client_proto", 36 | "//google/api:field_behavior_proto", 37 | "//google/api:resource_proto", 38 | "//google/rpc:status_proto", 39 | "@com_google_protobuf//:any_proto", 40 | "@com_google_protobuf//:duration_proto", 41 | "@com_google_protobuf//:field_mask_proto", 42 | "@com_google_protobuf//:empty_proto", 43 | "@com_google_protobuf//:timestamp_proto", 44 | "@com_google_protobuf//:wrappers_proto", 45 | ], 46 | ) 47 | 48 | proto_library_with_info( 49 | name = "resultstore_proto_with_info", 50 | deps = [ 51 | ":resultstore_proto", 52 | "//google/cloud:common_resources_proto", 53 | ], 54 | ) 55 | 56 | load( 57 | "@com_google_googleapis_imports//:imports.bzl", 58 | "moved_proto_library", 59 | "py_gapic_assembly_pkg", 60 | "py_gapic_library", 61 | "py_grpc_library", 62 | "py_proto_library", 63 | ) 64 | 65 | moved_proto_library( 66 | name = "resultstore_moved_proto", 67 | srcs = [":resultstore_proto"], 68 | deps = [ 69 | "//google/api:annotations_proto", 70 | "//google/api:client_proto", 71 | "//google/api:field_behavior_proto", 72 | "//google/api:resource_proto", 73 | "//google/rpc:status_proto", 74 | "@com_google_protobuf//:any_proto", 75 | "@com_google_protobuf//:duration_proto", 76 | "@com_google_protobuf//:field_mask_proto", 77 | "@com_google_protobuf//:empty_proto", 78 | "@com_google_protobuf//:timestamp_proto", 79 | "@com_google_protobuf//:wrappers_proto", 80 | ], 81 | ) 82 | 83 | py_proto_library( 84 | name = "resultstore_py_proto", 85 | plugin = "@protoc_docs_plugin//:docs_plugin", 86 | deps = [":resultstore_moved_proto"], 87 | ) 88 | 89 | py_grpc_library( 90 | name = "resultstore_py_grpc", 91 | srcs = [":resultstore_moved_proto"], 92 | deps = [":resultstore_py_proto"], 93 | ) 94 | 95 | py_gapic_library( 96 | name = "resultstore_py_gapic", 97 | src = ":resultstore_proto_with_info", 98 | gapic_yaml = "resultstore_gapic.yaml", 99 | package = "google.devtools.resultstore.v2", 100 | service_yaml = "resultstore_v2.yaml", 101 | deps = [ 102 | ":resultstore_py_grpc", 103 | ":resultstore_py_proto", 104 | ], 105 | ) 106 | 107 | # Open Source Packages 108 | py_gapic_assembly_pkg( 109 | name = "devtools-resultstore-v2-py", 110 | deps = [ 111 | ":resultstore_py_gapic", 112 | ":resultstore_py_grpc", 113 | ":resultstore_py_proto", 114 | ], 115 | ) 116 | -------------------------------------------------------------------------------- /resultstoreui/README.md: -------------------------------------------------------------------------------- 1 | # ResultStoreUI 2 | 3 | ## Usage 4 | 5 | This tool can be run using either 6 | 7 | ```shell 8 | bazel run resultstoreui 9 | ``` 10 | 11 | or 12 | 13 | ```shell 14 | python3 resultstoreui.py 15 | ``` 16 | 17 | and specifying the following flags 18 | 19 | - `--command`: The main command to be run for the current invocation of the cli 20 | eg. 21 | 22 | - `get-invocation` : Attempts to get the invocation specified with the `--invocation_name` flag. 23 | 24 | ```shell 25 | bazel run resultstoreui -- --command="get-invocation" --invocation_name="invocations/d9910974-4d59-4fee-8188-a891de97814a" 26 | ``` 27 | 28 | - `create-invocation`: Attempts to create a new invocation using the optionally provided `--authorization_token` or generating one to be associated with requests made with this invocation in the future. Specifying a `--resume_token` will put the invocation into batch mode. 29 | 30 | ```shell 31 | bazel run resultstoreui -- --command="create-invocation" --authorization_token="77f3d6ca-0577-429f-ba59-02090d27a15b" 32 | ``` 33 | 34 | - `single-upload`: Uploads a single target, config, configured target and files to the specified `--invocation-id` 35 | 36 | ```shell 37 | bazel run resultstoreui -- --command="single_upload" 38 | --invocation_id="d9910974-4d59-4fee-8188-a891de97814a" 39 | --authorization_token="77f3d6ca-0577-429f-ba59-02090d27a15b" 40 | --files="/path/to/file1, /path/to/file2" 41 | ``` 42 | 43 | - `batch-upload`: Batch uploads targets, configs, configured targets and files to an invocation in batch mode. 44 | 45 | ```shell 46 | bazel run resultstoreui -- --command="batch-upload" 47 | --invocation_id="d9910974-4d59-4fee-8188-a891de97814a" 48 | --authorization_token="77f3d6ca-0577-429f-ba59-02090d27a15b" 49 | --resume_token="current-resume-token" 50 | --next_resume_token="next-resume-token" 51 | --files="/path/to/file1, /path/to/file2" 52 | ``` 53 | 54 | - `finalize-batch-upload` : Finalizes an invocation in batch mode 55 | 56 | ```shell 57 | bazel run resultstoreui -- --command="finalize-batch-upload" 58 | --invocation_id="d9910974-4d59-4fee-8188-a891de97814a" 59 | --resume_token="current-resume-token" 60 | --next_resume_token="next-resume-token" 61 | --authorization_token="77f3d6ca-0577-429f-ba59-02090d27a15b" 62 | ``` 63 | 64 | - `--channel_target`: The host and port of the service that the channel is created to 65 | - `--config_id` : config_id to be used when creating a config 66 | - `--target_name`: target_name to be used when creating a target 67 | - `--invocation_id`: invocation to edit 68 | - `--authorization_token`: authorization token to be used during the current command 69 | - `--action_type`: action_type for the current action to be created 70 | - `--invocation_name`: action to get when using the `get-invocation` command 71 | - `--bigstore_project_name`: bigstore project to upload files to 72 | - `--bucket_name`: Bucket name inside bigstore to upload files to 73 | - `--files`: comma separated list of paths to files to upload for the current target 74 | - `--status`: Current status of the action being uploaded 75 | - `--create_config`: Boolean to specify creatione of a configuration during single-upload or batch-upload 76 | - `--resume_token`: Current resume token for batch uploads 77 | - `--next_resume_token`: Next resume token for batch uploads 78 | 79 | ## Running Tests 80 | 81 | Tests can be run with either 82 | 83 | ```shell 84 | bazel run test_resultstoreui 85 | ``` 86 | 87 | or 88 | 89 | ```shell 90 | python3 resultstore_client_test.py 91 | ``` 92 | 93 | ## Rebuilding gRPC Client Files 94 | 95 | 1. Clone https://github.com/googleapis/googleapis 96 | 97 | 2. Copy the build file from gRPC-build to the `google/devtools/resultstore/v2/` subdirectory 98 | 99 | 3. `bazel build //...` 100 | 101 | 4. Navigate to your local bazel cache under `${HOME}/.cache/bazel/_bazel_${USER}/BUILD_ID/execroot/com_google_googleapis` 102 | 103 | 5. Copy the google subdirectory and replace the current resultstoreapi folder with the newly generated files 104 | -------------------------------------------------------------------------------- /resultstoresearch/resultstoresearch/v1/target.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package resultstoresearch.v1; 4 | 5 | import "common.proto"; 6 | import "file.proto"; 7 | 8 | // Each Target represents data for a given target in a given Invocation. 9 | // ConfiguredTarget and Action resources under each Target contain the bulk of 10 | // the data. 11 | message Target { 12 | // The resource ID components that identify the Target. 13 | message Id { 14 | // The Invocation ID. 15 | string invocation_id = 1; 16 | 17 | // The Target ID. 18 | string target_id = 2; 19 | } 20 | 21 | // The resource name. Its format must be: 22 | // invocations/${INVOCATION_ID}/targets/${url_encode(TARGET_ID)} 23 | string name = 1; 24 | 25 | // The resource ID components that identify the Target. They must match the 26 | // resource name after proper encoding. 27 | Id id = 2; 28 | 29 | // This is the aggregate status of the target. 30 | StatusAttributes status_attributes = 3; 31 | 32 | // When this target started and its duration. 33 | Timing timing = 4; 34 | 35 | // Attributes that apply to all targets. 36 | TargetAttributes target_attributes = 5; 37 | 38 | // Attributes that apply to all test actions under this target. 39 | TestAttributes test_attributes = 6; 40 | 41 | // Arbitrary name-value pairs. 42 | // This is implemented as a multi-map. Multiple properties are allowed with 43 | // the same key. Properties will be returned in lexicographical order by key. 44 | repeated Property properties = 7; 45 | 46 | // A list of file references for target level files. 47 | // The file IDs must be unique within this list. Duplicate file IDs will 48 | // result in an error. Files will be returned in lexicographical order by ID. 49 | // Use this field to specify outputs not related to a configuration. 50 | repeated File files = 8; 51 | 52 | // Provides a hint to clients as to whether to display the Target to users. 53 | // If true then clients likely want to display the Target by default. 54 | // Once set to true, this may not be set back to false. 55 | bool visible = 10; 56 | } 57 | 58 | // Attributes that apply to all targets. 59 | message TargetAttributes { 60 | // If known, indicates the type of this target. In bazel this corresponds 61 | // to the rule-suffix. 62 | TargetType type = 1; 63 | 64 | // If known, the main language of this target, e.g. java, cc, python, etc. 65 | Language language = 2; 66 | 67 | // The tags attribute of the build rule. These should be short, descriptive 68 | // words, and there should only be a few of them. 69 | // This is implemented as a set. All tags will be unique. Any duplicate tags 70 | // will be ignored. Tags will be returned in lexicographical order. 71 | repeated string tags = 3; 72 | } 73 | 74 | // Attributes that apply only to test actions under this target. 75 | message TestAttributes { 76 | // Indicates how big the user indicated the test action was. 77 | TestSize size = 1; 78 | } 79 | 80 | // These correspond to the suffix of the rule name. Eg cc_test has type TEST. 81 | enum TargetType { 82 | // Unspecified by the build system. 83 | TARGET_TYPE_UNSPECIFIED = 0; 84 | 85 | // An application e.g. ios_application. 86 | APPLICATION = 1; 87 | 88 | // A binary target e.g. cc_binary. 89 | BINARY = 2; 90 | 91 | // A library target e.g. java_library 92 | LIBRARY = 3; 93 | 94 | // A package 95 | PACKAGE = 4; 96 | 97 | // Any test target, in bazel that means a rule with a '_test' suffix. 98 | TEST = 5; 99 | } 100 | 101 | // Indicates how big the user indicated the test action was. 102 | enum TestSize { 103 | // Unspecified by the user. 104 | TEST_SIZE_UNSPECIFIED = 0; 105 | 106 | // Unit test taking less than 1 minute. 107 | SMALL = 1; 108 | 109 | // Integration tests taking less than 5 minutes. 110 | MEDIUM = 2; 111 | 112 | // End-to-end tests taking less than 15 minutes. 113 | LARGE = 3; 114 | 115 | // Even bigger than LARGE. 116 | ENORMOUS = 4; 117 | 118 | // Something that doesn't fit into the above categories. 119 | OTHER_SIZE = 5; 120 | } 121 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/devtools/resultstore_v2/proto/download_metadata_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: google/cloud/devtools/resultstore_v2/proto/download_metadata.proto 4 | 5 | from google.protobuf import descriptor as _descriptor 6 | from google.protobuf import message as _message 7 | from google.protobuf import reflection as _reflection 8 | from google.protobuf import symbol_database as _symbol_database 9 | # @@protoc_insertion_point(imports) 10 | 11 | _sym_db = _symbol_database.Default() 12 | 13 | 14 | from resultstoreapi.cloud.devtools.resultstore_v2.proto import common_pb2 as google_dot_cloud_dot_devtools_dot_resultstore__v2_dot_proto_dot_common__pb2 15 | 16 | 17 | DESCRIPTOR = _descriptor.FileDescriptor( 18 | name='google/cloud/devtools/resultstore_v2/proto/download_metadata.proto', 19 | package='google.devtools.resultstore.v2', 20 | syntax='proto3', 21 | serialized_options=b'\n\"com.google.devtools.resultstore.v2P\001ZIgoogle.golang.org/genproto/googleapis/devtools/resultstore/v2;resultstore', 22 | serialized_pb=b'\nBgoogle/cloud/devtools/resultstore_v2/proto/download_metadata.proto\x12\x1egoogle.devtools.resultstore.v2\x1a\x37google/cloud/devtools/resultstore_v2/proto/common.proto\"e\n\x10\x44ownloadMetadata\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x43\n\rupload_status\x18\x02 \x01(\x0e\x32,.google.devtools.resultstore.v2.UploadStatusBq\n\"com.google.devtools.resultstore.v2P\x01ZIgoogle.golang.org/genproto/googleapis/devtools/resultstore/v2;resultstoreb\x06proto3' 23 | , 24 | dependencies=[google_dot_cloud_dot_devtools_dot_resultstore__v2_dot_proto_dot_common__pb2.DESCRIPTOR,]) 25 | 26 | 27 | 28 | 29 | _DOWNLOADMETADATA = _descriptor.Descriptor( 30 | name='DownloadMetadata', 31 | full_name='google.devtools.resultstore.v2.DownloadMetadata', 32 | filename=None, 33 | file=DESCRIPTOR, 34 | containing_type=None, 35 | fields=[ 36 | _descriptor.FieldDescriptor( 37 | name='name', full_name='google.devtools.resultstore.v2.DownloadMetadata.name', index=0, 38 | number=1, type=9, cpp_type=9, label=1, 39 | has_default_value=False, default_value=b"".decode('utf-8'), 40 | message_type=None, enum_type=None, containing_type=None, 41 | is_extension=False, extension_scope=None, 42 | serialized_options=None, file=DESCRIPTOR), 43 | _descriptor.FieldDescriptor( 44 | name='upload_status', full_name='google.devtools.resultstore.v2.DownloadMetadata.upload_status', index=1, 45 | number=2, type=14, cpp_type=8, label=1, 46 | has_default_value=False, default_value=0, 47 | message_type=None, enum_type=None, containing_type=None, 48 | is_extension=False, extension_scope=None, 49 | serialized_options=None, file=DESCRIPTOR), 50 | ], 51 | extensions=[ 52 | ], 53 | nested_types=[], 54 | enum_types=[ 55 | ], 56 | serialized_options=None, 57 | is_extendable=False, 58 | syntax='proto3', 59 | extension_ranges=[], 60 | oneofs=[ 61 | ], 62 | serialized_start=159, 63 | serialized_end=260, 64 | ) 65 | 66 | _DOWNLOADMETADATA.fields_by_name['upload_status'].enum_type = google_dot_cloud_dot_devtools_dot_resultstore__v2_dot_proto_dot_common__pb2._UPLOADSTATUS 67 | DESCRIPTOR.message_types_by_name['DownloadMetadata'] = _DOWNLOADMETADATA 68 | _sym_db.RegisterFileDescriptor(DESCRIPTOR) 69 | 70 | DownloadMetadata = _reflection.GeneratedProtocolMessageType('DownloadMetadata', (_message.Message,), { 71 | 'DESCRIPTOR' : _DOWNLOADMETADATA, 72 | '__module__' : 'resultstoreapi.cloud.devtools.resultstore_v2.proto.download_metadata_pb2' 73 | , 74 | '__doc__': """The download metadata for an invocation 75 | 76 | 77 | Attributes: 78 | name: 79 | The name of the download metadata. Its format will be: 80 | invocations/${INVOCATION_ID}/downloadMetadata 81 | upload_status: 82 | Indicates the upload status of the invocation, whether it is 83 | post-processing, or immutable, etc. 84 | """, 85 | # @@protoc_insertion_point(class_scope:google.devtools.resultstore.v2.DownloadMetadata) 86 | }) 87 | _sym_db.RegisterMessage(DownloadMetadata) 88 | 89 | 90 | DESCRIPTOR._options = None 91 | # @@protoc_insertion_point(module_scope) 92 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/devtools/resultstore_v2/proto/download_metadata_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: google/cloud/devtools/resultstore_v2/proto/download_metadata.proto 4 | 5 | from google.protobuf import descriptor as _descriptor 6 | from google.protobuf import message as _message 7 | from google.protobuf import reflection as _reflection 8 | from google.protobuf import symbol_database as _symbol_database 9 | # @@protoc_insertion_point(imports) 10 | 11 | _sym_db = _symbol_database.Default() 12 | 13 | 14 | from resultstoreapi.cloud.devtools.resultstore_v2.proto import common_pb2 as google_dot_cloud_dot_devtools_dot_resultstore__v2_dot_proto_dot_common__pb2 15 | 16 | 17 | DESCRIPTOR = _descriptor.FileDescriptor( 18 | name='google/cloud/devtools/resultstore_v2/proto/download_metadata.proto', 19 | package='google.devtools.resultstore.v2', 20 | syntax='proto3', 21 | serialized_options=b'\n\"com.google.devtools.resultstore.v2P\001ZIgoogle.golang.org/genproto/googleapis/devtools/resultstore/v2;resultstore', 22 | serialized_pb=b'\nBgoogle/cloud/devtools/resultstore_v2/proto/download_metadata.proto\x12\x1egoogle.devtools.resultstore.v2\x1a\x37google/cloud/devtools/resultstore_v2/proto/common.proto\"e\n\x10\x44ownloadMetadata\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x43\n\rupload_status\x18\x02 \x01(\x0e\x32,.google.devtools.resultstore.v2.UploadStatusBq\n\"com.google.devtools.resultstore.v2P\x01ZIgoogle.golang.org/genproto/googleapis/devtools/resultstore/v2;resultstoreb\x06proto3' 23 | , 24 | dependencies=[google_dot_cloud_dot_devtools_dot_resultstore__v2_dot_proto_dot_common__pb2.DESCRIPTOR,]) 25 | 26 | 27 | 28 | 29 | _DOWNLOADMETADATA = _descriptor.Descriptor( 30 | name='DownloadMetadata', 31 | full_name='google.devtools.resultstore.v2.DownloadMetadata', 32 | filename=None, 33 | file=DESCRIPTOR, 34 | containing_type=None, 35 | fields=[ 36 | _descriptor.FieldDescriptor( 37 | name='name', full_name='google.devtools.resultstore.v2.DownloadMetadata.name', index=0, 38 | number=1, type=9, cpp_type=9, label=1, 39 | has_default_value=False, default_value=b"".decode('utf-8'), 40 | message_type=None, enum_type=None, containing_type=None, 41 | is_extension=False, extension_scope=None, 42 | serialized_options=None, file=DESCRIPTOR), 43 | _descriptor.FieldDescriptor( 44 | name='upload_status', full_name='google.devtools.resultstore.v2.DownloadMetadata.upload_status', index=1, 45 | number=2, type=14, cpp_type=8, label=1, 46 | has_default_value=False, default_value=0, 47 | message_type=None, enum_type=None, containing_type=None, 48 | is_extension=False, extension_scope=None, 49 | serialized_options=None, file=DESCRIPTOR), 50 | ], 51 | extensions=[ 52 | ], 53 | nested_types=[], 54 | enum_types=[ 55 | ], 56 | serialized_options=None, 57 | is_extendable=False, 58 | syntax='proto3', 59 | extension_ranges=[], 60 | oneofs=[ 61 | ], 62 | serialized_start=159, 63 | serialized_end=260, 64 | ) 65 | 66 | _DOWNLOADMETADATA.fields_by_name['upload_status'].enum_type = google_dot_cloud_dot_devtools_dot_resultstore__v2_dot_proto_dot_common__pb2._UPLOADSTATUS 67 | DESCRIPTOR.message_types_by_name['DownloadMetadata'] = _DOWNLOADMETADATA 68 | _sym_db.RegisterFileDescriptor(DESCRIPTOR) 69 | 70 | DownloadMetadata = _reflection.GeneratedProtocolMessageType('DownloadMetadata', (_message.Message,), { 71 | 'DESCRIPTOR' : _DOWNLOADMETADATA, 72 | '__module__' : 'resultstoreapi.cloud.devtools.resultstore_v2.proto.download_metadata_pb2' 73 | , 74 | '__doc__': """The download metadata for an invocation 75 | 76 | 77 | Attributes: 78 | name: 79 | The name of the download metadata. Its format will be: 80 | invocations/${INVOCATION_ID}/downloadMetadata 81 | upload_status: 82 | Indicates the upload status of the invocation, whether it is 83 | post-processing, or immutable, etc. 84 | """, 85 | # @@protoc_insertion_point(class_scope:google.devtools.resultstore.v2.DownloadMetadata) 86 | }) 87 | _sym_db.RegisterMessage(DownloadMetadata) 88 | 89 | 90 | DESCRIPTOR._options = None 91 | # @@protoc_insertion_point(module_scope) 92 | -------------------------------------------------------------------------------- /resultstoreui/resultstoreapi/cloud/devtools/resultstore_v2/proto/upload_metadata_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: google/cloud/devtools/resultstore_v2/proto/upload_metadata.proto 4 | 5 | from google.protobuf import descriptor as _descriptor 6 | from google.protobuf import message as _message 7 | from google.protobuf import reflection as _reflection 8 | from google.protobuf import symbol_database as _symbol_database 9 | # @@protoc_insertion_point(imports) 10 | 11 | _sym_db = _symbol_database.Default() 12 | 13 | 14 | 15 | 16 | DESCRIPTOR = _descriptor.FileDescriptor( 17 | name='google/cloud/devtools/resultstore_v2/proto/upload_metadata.proto', 18 | package='google.devtools.resultstore.v2', 19 | syntax='proto3', 20 | serialized_options=b'\n\"com.google.devtools.resultstore.v2P\001ZIgoogle.golang.org/genproto/googleapis/devtools/resultstore/v2;resultstore', 21 | serialized_pb=b'\n@google/cloud/devtools/resultstore_v2/proto/upload_metadata.proto\x12\x1egoogle.devtools.resultstore.v2\"L\n\x0eUploadMetadata\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x14\n\x0cresume_token\x18\x02 \x01(\t\x12\x16\n\x0euploader_state\x18\x03 \x01(\x0c\x42q\n\"com.google.devtools.resultstore.v2P\x01ZIgoogle.golang.org/genproto/googleapis/devtools/resultstore/v2;resultstoreb\x06proto3' 22 | ) 23 | 24 | 25 | 26 | 27 | _UPLOADMETADATA = _descriptor.Descriptor( 28 | name='UploadMetadata', 29 | full_name='google.devtools.resultstore.v2.UploadMetadata', 30 | filename=None, 31 | file=DESCRIPTOR, 32 | containing_type=None, 33 | fields=[ 34 | _descriptor.FieldDescriptor( 35 | name='name', full_name='google.devtools.resultstore.v2.UploadMetadata.name', index=0, 36 | number=1, type=9, cpp_type=9, label=1, 37 | has_default_value=False, default_value=b"".decode('utf-8'), 38 | message_type=None, enum_type=None, containing_type=None, 39 | is_extension=False, extension_scope=None, 40 | serialized_options=None, file=DESCRIPTOR), 41 | _descriptor.FieldDescriptor( 42 | name='resume_token', full_name='google.devtools.resultstore.v2.UploadMetadata.resume_token', index=1, 43 | number=2, type=9, cpp_type=9, label=1, 44 | has_default_value=False, default_value=b"".decode('utf-8'), 45 | message_type=None, enum_type=None, containing_type=None, 46 | is_extension=False, extension_scope=None, 47 | serialized_options=None, file=DESCRIPTOR), 48 | _descriptor.FieldDescriptor( 49 | name='uploader_state', full_name='google.devtools.resultstore.v2.UploadMetadata.uploader_state', index=2, 50 | number=3, type=12, cpp_type=9, label=1, 51 | has_default_value=False, default_value=b"", 52 | message_type=None, enum_type=None, containing_type=None, 53 | is_extension=False, extension_scope=None, 54 | serialized_options=None, file=DESCRIPTOR), 55 | ], 56 | extensions=[ 57 | ], 58 | nested_types=[], 59 | enum_types=[ 60 | ], 61 | serialized_options=None, 62 | is_extendable=False, 63 | syntax='proto3', 64 | extension_ranges=[], 65 | oneofs=[ 66 | ], 67 | serialized_start=100, 68 | serialized_end=176, 69 | ) 70 | 71 | DESCRIPTOR.message_types_by_name['UploadMetadata'] = _UPLOADMETADATA 72 | _sym_db.RegisterFileDescriptor(DESCRIPTOR) 73 | 74 | UploadMetadata = _reflection.GeneratedProtocolMessageType('UploadMetadata', (_message.Message,), { 75 | 'DESCRIPTOR' : _UPLOADMETADATA, 76 | '__module__' : 'resultstoreapi.cloud.devtools.resultstore_v2.proto.upload_metadata_pb2' 77 | , 78 | '__doc__': """The upload metadata for an invocation 79 | 80 | 81 | Attributes: 82 | name: 83 | The name of the upload metadata. Its format will be: 84 | invocations/${INVOCATION_ID}/uploadMetadata 85 | resume_token: 86 | The resume token of the last batch that was committed in the 87 | most recent batch upload. More information with resume_token 88 | could be found in resultstore_upload.proto 89 | uploader_state: 90 | Client-specific data used to resume batch upload if an error 91 | occurs and retry action is needed. 92 | """, 93 | # @@protoc_insertion_point(class_scope:google.devtools.resultstore.v2.UploadMetadata) 94 | }) 95 | _sym_db.RegisterMessage(UploadMetadata) 96 | 97 | 98 | DESCRIPTOR._options = None 99 | # @@protoc_insertion_point(module_scope) 100 | -------------------------------------------------------------------------------- /resultstoresearch/server/resultstoresearch/resultstoreapi/cloud/devtools/resultstore_v2/proto/upload_metadata_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: google/cloud/devtools/resultstore_v2/proto/upload_metadata.proto 4 | 5 | from google.protobuf import descriptor as _descriptor 6 | from google.protobuf import message as _message 7 | from google.protobuf import reflection as _reflection 8 | from google.protobuf import symbol_database as _symbol_database 9 | # @@protoc_insertion_point(imports) 10 | 11 | _sym_db = _symbol_database.Default() 12 | 13 | 14 | 15 | 16 | DESCRIPTOR = _descriptor.FileDescriptor( 17 | name='google/cloud/devtools/resultstore_v2/proto/upload_metadata.proto', 18 | package='google.devtools.resultstore.v2', 19 | syntax='proto3', 20 | serialized_options=b'\n\"com.google.devtools.resultstore.v2P\001ZIgoogle.golang.org/genproto/googleapis/devtools/resultstore/v2;resultstore', 21 | serialized_pb=b'\n@google/cloud/devtools/resultstore_v2/proto/upload_metadata.proto\x12\x1egoogle.devtools.resultstore.v2\"L\n\x0eUploadMetadata\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x14\n\x0cresume_token\x18\x02 \x01(\t\x12\x16\n\x0euploader_state\x18\x03 \x01(\x0c\x42q\n\"com.google.devtools.resultstore.v2P\x01ZIgoogle.golang.org/genproto/googleapis/devtools/resultstore/v2;resultstoreb\x06proto3' 22 | ) 23 | 24 | 25 | 26 | 27 | _UPLOADMETADATA = _descriptor.Descriptor( 28 | name='UploadMetadata', 29 | full_name='google.devtools.resultstore.v2.UploadMetadata', 30 | filename=None, 31 | file=DESCRIPTOR, 32 | containing_type=None, 33 | fields=[ 34 | _descriptor.FieldDescriptor( 35 | name='name', full_name='google.devtools.resultstore.v2.UploadMetadata.name', index=0, 36 | number=1, type=9, cpp_type=9, label=1, 37 | has_default_value=False, default_value=b"".decode('utf-8'), 38 | message_type=None, enum_type=None, containing_type=None, 39 | is_extension=False, extension_scope=None, 40 | serialized_options=None, file=DESCRIPTOR), 41 | _descriptor.FieldDescriptor( 42 | name='resume_token', full_name='google.devtools.resultstore.v2.UploadMetadata.resume_token', index=1, 43 | number=2, type=9, cpp_type=9, label=1, 44 | has_default_value=False, default_value=b"".decode('utf-8'), 45 | message_type=None, enum_type=None, containing_type=None, 46 | is_extension=False, extension_scope=None, 47 | serialized_options=None, file=DESCRIPTOR), 48 | _descriptor.FieldDescriptor( 49 | name='uploader_state', full_name='google.devtools.resultstore.v2.UploadMetadata.uploader_state', index=2, 50 | number=3, type=12, cpp_type=9, label=1, 51 | has_default_value=False, default_value=b"", 52 | message_type=None, enum_type=None, containing_type=None, 53 | is_extension=False, extension_scope=None, 54 | serialized_options=None, file=DESCRIPTOR), 55 | ], 56 | extensions=[ 57 | ], 58 | nested_types=[], 59 | enum_types=[ 60 | ], 61 | serialized_options=None, 62 | is_extendable=False, 63 | syntax='proto3', 64 | extension_ranges=[], 65 | oneofs=[ 66 | ], 67 | serialized_start=100, 68 | serialized_end=176, 69 | ) 70 | 71 | DESCRIPTOR.message_types_by_name['UploadMetadata'] = _UPLOADMETADATA 72 | _sym_db.RegisterFileDescriptor(DESCRIPTOR) 73 | 74 | UploadMetadata = _reflection.GeneratedProtocolMessageType('UploadMetadata', (_message.Message,), { 75 | 'DESCRIPTOR' : _UPLOADMETADATA, 76 | '__module__' : 'resultstoreapi.cloud.devtools.resultstore_v2.proto.upload_metadata_pb2' 77 | , 78 | '__doc__': """The upload metadata for an invocation 79 | 80 | 81 | Attributes: 82 | name: 83 | The name of the upload metadata. Its format will be: 84 | invocations/${INVOCATION_ID}/uploadMetadata 85 | resume_token: 86 | The resume token of the last batch that was committed in the 87 | most recent batch upload. More information with resume_token 88 | could be found in resultstore_upload.proto 89 | uploader_state: 90 | Client-specific data used to resume batch upload if an error 91 | occurs and retry action is needed. 92 | """, 93 | # @@protoc_insertion_point(class_scope:google.devtools.resultstore.v2.UploadMetadata) 94 | }) 95 | _sym_db.RegisterMessage(UploadMetadata) 96 | 97 | 98 | DESCRIPTOR._options = None 99 | # @@protoc_insertion_point(module_scope) 100 | --------------------------------------------------------------------------------