diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000..ea91c9ecfe --- /dev/null +++ b/.clang-format @@ -0,0 +1 @@ +BasedOnStyle: google diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2eba8edd45..49e8d19e8f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -91,6 +91,19 @@ jobs: run: | ! git grep 'python_version = "PY2"' '*BUILD' + lint-proto: + runs-on: ubuntu-16.04 + steps: + - uses: actions/checkout@v1 + - name: clang-format lint + uses: DoozyX/clang-format-lint-action@v0.5 + with: + source: ./tensorboard + # Exclude tensorboard/compat because the source of truth is TensorFlow. + exclude: ./tensorboard/compat/proto + extensions: 'proto' + clangFormatVersion: 9 + check-misc: runs-on: ubuntu-16.04 steps: diff --git a/tensorboard/backend/empty_path_redirect_test.py b/tensorboard/backend/empty_path_redirect_test.py index 7a643e14c3..5b25a47b78 100644 --- a/tensorboard/backend/empty_path_redirect_test.py +++ b/tensorboard/backend/empty_path_redirect_test.py @@ -38,7 +38,9 @@ def setUp(self): app = empty_path_redirect.EmptyPathRedirectMiddleware(app) app = self._lax_strip_foo_middleware(app) self.app = app - self.server = werkzeug_test.Client(self.app, werkzeug.BaseResponse) + self.server = werkzeug_test.Client( + self.app, werkzeug.wrappers.BaseResponse + ) def _lax_strip_foo_middleware(self, app): """Strips a `/foo` prefix if it exists; no-op otherwise.""" diff --git a/tensorboard/backend/experiment_id_test.py b/tensorboard/backend/experiment_id_test.py index 5ebc7c9091..ff3f1a4e5f 100644 --- a/tensorboard/backend/experiment_id_test.py +++ b/tensorboard/backend/experiment_id_test.py @@ -33,7 +33,9 @@ class ExperimentIdMiddlewareTest(tb_test.TestCase): def setUp(self): super(ExperimentIdMiddlewareTest, self).setUp() self.app = experiment_id.ExperimentIdMiddleware(self._echo_app) - self.server = werkzeug_test.Client(self.app, werkzeug.BaseResponse) + self.server = werkzeug_test.Client( + self.app, werkzeug.wrappers.BaseResponse + ) def _echo_app(self, environ, start_response): # https://www.python.org/dev/peps/pep-0333/#environ-variables diff --git a/tensorboard/backend/path_prefix_test.py b/tensorboard/backend/path_prefix_test.py index 6b62fbf76f..d429baa2ea 100644 --- a/tensorboard/backend/path_prefix_test.py +++ b/tensorboard/backend/path_prefix_test.py @@ -63,7 +63,7 @@ def test_bad_path_prefix_with_trailing_slash(self): def test_empty_path_prefix(self): app = path_prefix.PathPrefixMiddleware(self._echo_app, "") - server = werkzeug_test.Client(app, werkzeug.BaseResponse) + server = werkzeug_test.Client(app, werkzeug.wrappers.BaseResponse) with self.subTest("at empty"): self._assert_ok(server.get(""), path="", script="") @@ -77,7 +77,7 @@ def test_empty_path_prefix(self): def test_nonempty_path_prefix(self): app = path_prefix.PathPrefixMiddleware(self._echo_app, "/pfx") - server = werkzeug_test.Client(app, werkzeug.BaseResponse) + server = werkzeug_test.Client(app, werkzeug.wrappers.BaseResponse) with self.subTest("at root"): response = server.get("/pfx") @@ -103,7 +103,7 @@ def test_composition(self): app = self._echo_app app = path_prefix.PathPrefixMiddleware(app, "/bar") app = path_prefix.PathPrefixMiddleware(app, "/foo") - server = werkzeug_test.Client(app, werkzeug.BaseResponse) + server = werkzeug_test.Client(app, werkzeug.wrappers.BaseResponse) response = server.get("/foo/bar/baz/quux") self._assert_ok(response, path="/baz/quux", script="/foo/bar") diff --git a/tensorboard/plugins/custom_scalar/layout.proto b/tensorboard/plugins/custom_scalar/layout.proto index 0a3342644f..f26204ea72 100644 --- a/tensorboard/plugins/custom_scalar/layout.proto +++ b/tensorboard/plugins/custom_scalar/layout.proto @@ -17,7 +17,6 @@ syntax = "proto3"; package tensorboard; - /** * Encapsulates information on a single chart. Many charts appear in a category. */ diff --git a/tensorboard/uploader/proto/BUILD b/tensorboard/uploader/proto/BUILD index bb702af388..509fd894aa 100644 --- a/tensorboard/uploader/proto/BUILD +++ b/tensorboard/uploader/proto/BUILD @@ -10,6 +10,7 @@ exports_files(["LICENSE"]) tb_proto_library( name = "protos_all", srcs = [ + "experiment.proto", "export_service.proto", "scalar.proto", "server_info.proto", diff --git a/tensorboard/uploader/proto/experiment.proto b/tensorboard/uploader/proto/experiment.proto new file mode 100644 index 0000000000..72ac875ae1 --- /dev/null +++ b/tensorboard/uploader/proto/experiment.proto @@ -0,0 +1,43 @@ +syntax = "proto3"; + +package tensorboard.service; + +import "google/protobuf/timestamp.proto"; + +// Metadata about an experiment. +message Experiment { + // Permanent ID of this experiment; e.g.: "AdYd1TgeTlaLWXx6I8JUbA". + string experiment_id = 1; + // The time that the experiment was created. + google.protobuf.Timestamp create_time = 2; + // The time that the experiment was last modified: i.e., the most recent time + // that scalars were added to the experiment. + google.protobuf.Timestamp update_time = 3; + // The number of scalars in this experiment, across all time series. + int64 num_scalars = 4; + // The number of distinct run names in this experiment. + int64 num_runs = 5; + // The number of distinct tag names in this experiment. A tag name that + // appears in multiple runs will be counted only once. + int64 num_tags = 6; + // User provided name of the experiment. + string name = 7; + // User provided description of the experiment, in markdown source format. + string description = 8; +} + +// Field mask for `Experiment`. The `experiment_id` field is always implicitly +// considered to be requested. Other fields of `Experiment` will be populated +// if their corresponding bits in the `ExperimentMask` are set. The server may +// choose to populate fields that are not explicitly requested. +message ExperimentMask { + reserved 1; + reserved "experiment_id"; + bool create_time = 2; + bool update_time = 3; + bool num_scalars = 4; + bool num_runs = 5; + bool num_tags = 6; + bool name = 7; + bool description = 8; +} diff --git a/tensorboard/uploader/proto/export_service.proto b/tensorboard/uploader/proto/export_service.proto index a8dba432dc..26c243a2d0 100644 --- a/tensorboard/uploader/proto/export_service.proto +++ b/tensorboard/uploader/proto/export_service.proto @@ -72,6 +72,7 @@ message StreamExperimentsResponse { } // Metadata about an experiment. +// TODO(bileschi): Centralize on the Experiment in experiment.proto message Experiment { // Permanent ID of this experiment; e.g.: "AdYd1TgeTlaLWXx6I8JUbA". string experiment_id = 1; diff --git a/tensorboard/uploader/proto/write_service.proto b/tensorboard/uploader/proto/write_service.proto index 4d63f22f69..fa5b979aa8 100644 --- a/tensorboard/uploader/proto/write_service.proto +++ b/tensorboard/uploader/proto/write_service.proto @@ -2,6 +2,7 @@ syntax = "proto3"; package tensorboard.service; +import "tensorboard/uploader/proto/experiment.proto"; import "tensorboard/uploader/proto/scalar.proto"; import "tensorboard/compat/proto/summary.proto"; @@ -23,13 +24,19 @@ service TensorBoardWriterService { // Request that the calling user and all their data be permanently deleted. // Used for testing purposes. rpc DeleteOwnUser(DeleteOwnUserRequest) returns (DeleteOwnUserResponse) {} + // Request to mutate metadata associated with an experiment. + rpc UpdateExperimentMetadata(UpdateExperimentMetadataRequest) + returns (UpdateExperimentMetadataResponse) {} } // This is currently empty on purpose. No information is necessary // to request a URL, except. authorization of course, which doesn't // come within the proto. message CreateExperimentRequest { - // This is empty on purpose. + // User provided name of the experiment. + string name = 1; + // User provided description of the experiment, in markdown source format. + string description = 2; } // Carries all information necessary to: @@ -128,3 +135,22 @@ message DeleteOwnUserRequest { message DeleteOwnUserResponse { // This is empty on purpose. } + +// Request to change the metadata of one experiment. +message UpdateExperimentMetadataRequest { + // Description of the data to set. The experiment_id field must match + // an experiment_id in the database. The remaining fields should be set + // to the desired metadata to be written. Only those fields marked True + // in the experiment_mask will be written. The service may deny + // modification of some metadata used for internal bookkeeping, such as + // num_scalars, etc. + Experiment experiment = 1; + // Field mask for what experiment data to set. The service may deny requests + // to set some metatadata. + ExperimentMask experiment_mask = 2; +} + +// Response for setting experiment name. +message UpdateExperimentMetadataResponse { + // This is empty on purpose. +} diff --git a/tensorboard/uploader/uploader_main.py b/tensorboard/uploader/uploader_main.py index 5ed906ea85..9adea8c18f 100644 --- a/tensorboard/uploader/uploader_main.py +++ b/tensorboard/uploader/uploader_main.py @@ -409,6 +409,8 @@ def execute(self, server_info, channel): url = server_info_lib.experiment_url(server_info, experiment_id) print(url) data = [ + ("Name", experiment.name), + ("Description", experiment.description), ("Id", experiment.experiment_id), ("Created", util.format_time(experiment.create_time)), ("Updated", util.format_time(experiment.update_time)), diff --git a/tensorboard/webapp/webapp_data_source/BUILD b/tensorboard/webapp/webapp_data_source/BUILD index 7b1d0b6058..853b6a52e2 100644 --- a/tensorboard/webapp/webapp_data_source/BUILD +++ b/tensorboard/webapp/webapp_data_source/BUILD @@ -41,5 +41,6 @@ ng_module( deps = [ ":http_client", "//tensorboard/webapp/angular:expect_angular_common_http_testing", + "@npm//@angular/core", ], ) diff --git a/tensorboard/webapp/webapp_data_source/tb_server_data_source_module.ts b/tensorboard/webapp/webapp_data_source/tb_server_data_source_module.ts index 5df82fc121..0c8fa6f594 100644 --- a/tensorboard/webapp/webapp_data_source/tb_server_data_source_module.ts +++ b/tensorboard/webapp/webapp_data_source/tb_server_data_source_module.ts @@ -13,7 +13,6 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ import {NgModule} from '@angular/core'; -import {HttpClientModule} from '@angular/common/http'; import {TBHttpClientModule} from './tb_http_client_module'; import {TBServerDataSource} from './tb_server_data_source'; diff --git a/third_party/fonts.bzl b/third_party/fonts.bzl index 15de6dd997..56c3bc9d02 100644 --- a/third_party/fonts.bzl +++ b/third_party/fonts.bzl @@ -13,7 +13,6 @@ # limitations under the License. load("@io_bazel_rules_closure//closure:defs.bzl", "filegroup_external") -load("@io_bazel_rules_closure//closure:defs.bzl", "web_library_external") def tensorboard_fonts_workspace(): """Downloads TensorBoard fonts."""