This is an automated email from the ASF dual-hosted git repository.

damccorm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git


The following commit(s) were added to refs/heads/master by this push:
     new 1c4e4241c2a [Playground][Frontend] Run timer fix; CodeRunner and 
OutputType extraction. (#24871)
1c4e4241c2a is described below

commit 1c4e4241c2a4571c7d6c9f293133b114a9e00844
Author: Darkhan Nausharipov <31556582+naushari...@users.noreply.github.com>
AuthorDate: Thu Jan 19 19:59:31 2023 +0600

    [Playground][Frontend] Run timer fix; CodeRunner and OutputType extraction. 
(#24871)
    
    * timer fix rough draft (#24617)
    
    * comments (#24617)
    
    * comments (1) (#24617)
    
    * cancel run on reset (#24617)
    
    * regenerated mocks (#24617)
    
    * runStopDate (#24617)
    
    * OutputFilterTypeController (#24617)
    
    * resetErrorMessageText (#24617)
    
    Co-authored-by: alexeyinkin <l...@inkin.ru>
    
    * resetErrorMessageText (#24617)
    
    Co-authored-by: alexeyinkin <l...@inkin.ru>
    
    * comments (#24617)
    
    * missing license (#24617)
    
    * a commit for re-running rat check on github
    
    * deleted comment "a commit for re-running rat check on github"
    
    * fixes after merge with master (#24617)
    
    * merge fixes (#24617)
    
    * regenerated yaml files (#24617)
    
    * snippetEditingControllerGetter refinement (#24617)
    
    Co-authored-by: darkhan.nausharipov <darkhan.naushari...@kzn.akvelon.com>
    Co-authored-by: alexeyinkin <l...@inkin.ru>
---
 .../shortcuts/constants/global_shortcuts.dart      |   2 +-
 .../widgets/close_listener.dart                    |   4 +-
 .../widgets/editor_textarea_wrapper.dart           |   7 +-
 playground/frontend/lib/utils/analytics_utils.dart |   2 +-
 .../playground_components/assets/symbols/go.g.yaml |  89 ++++-
 .../assets/symbols/java.g.yaml                     | 415 +++++++++++++++++++--
 .../assets/symbols/python.g.yaml                   |  36 +-
 .../lib/src/controllers/code_runner.dart           | 165 ++++++++
 .../output_filter_type_controller.dart}            |  24 +-
 .../lib/src/controllers/playground_controller.dart | 210 ++---------
 .../lib/src/models/outputs.dart                    |   1 -
 .../lib/src/widgets/output/output_area.dart        |  62 +--
 .../lib/src/widgets/output/output_tab.dart         |  10 +-
 .../lib/src/widgets/output/output_tabs.dart        |   5 +-
 .../src/widgets/output/result_filter_bubble.dart   |   6 +-
 .../src/widgets/output/result_filter_popover.dart  |   2 +-
 .../lib/src/widgets/periodic_builder.dart}         |  42 ++-
 .../lib/src/widgets/run_button.dart                | 142 ++++---
 .../lib/src/widgets/run_or_cancel_button.dart      |   8 +-
 .../examples_loader_test.mocks.dart                | 301 +++++++--------
 .../controllers/playground_controller_test.dart    |  19 +-
 21 files changed, 1006 insertions(+), 546 deletions(-)

diff --git 
a/playground/frontend/lib/modules/shortcuts/constants/global_shortcuts.dart 
b/playground/frontend/lib/modules/shortcuts/constants/global_shortcuts.dart
index 03797eaff3a..00687271773 100644
--- a/playground/frontend/lib/modules/shortcuts/constants/global_shortcuts.dart
+++ b/playground/frontend/lib/modules/shortcuts/constants/global_shortcuts.dart
@@ -40,7 +40,7 @@ final kClearOutputShortcut = BeamShortcut(
     onInvoke: (_) => Provider.of<PlaygroundController>(
       context,
       listen: false,
-    ).clearOutput(),
+    ).codeRunner.clearResult(),
   ),
 );
 
diff --git 
a/playground/frontend/lib/pages/standalone_playground/widgets/close_listener.dart
 
b/playground/frontend/lib/pages/standalone_playground/widgets/close_listener.dart
index ba2a19a7ae2..0a370f1334c 100644
--- 
a/playground/frontend/lib/pages/standalone_playground/widgets/close_listener.dart
+++ 
b/playground/frontend/lib/pages/standalone_playground/widgets/close_listener.dart
@@ -36,7 +36,9 @@ class _CloseListenerState extends State<CloseListener> {
   void initState() {
     WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
       html.window.onBeforeUnload.listen((event) async {
-        Provider.of<PlaygroundController>(context, listen: false).cancelRun();
+        Provider.of<PlaygroundController>(context, listen: false)
+            .codeRunner
+            .cancelRun();
       });
     });
     super.initState();
diff --git 
a/playground/frontend/lib/pages/standalone_playground/widgets/editor_textarea_wrapper.dart
 
b/playground/frontend/lib/pages/standalone_playground/widgets/editor_textarea_wrapper.dart
index 86b6a5045d6..552064498ea 100644
--- 
a/playground/frontend/lib/pages/standalone_playground/widgets/editor_textarea_wrapper.dart
+++ 
b/playground/frontend/lib/pages/standalone_playground/widgets/editor_textarea_wrapper.dart
@@ -35,7 +35,8 @@ class CodeTextAreaWrapper extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    if (playgroundController.result?.errorMessage?.isNotEmpty ?? false) {
+    if (playgroundController.codeRunner.result?.errorMessage?.isNotEmpty ??
+        false) {
       WidgetsBinding.instance.addPostFrameCallback((_) {
         _handleError(context, playgroundController);
       });
@@ -83,11 +84,11 @@ class CodeTextAreaWrapper extends StatelessWidget {
   void _handleError(BuildContext context, PlaygroundController controller) {
     PlaygroundComponents.toastNotifier.add(
       Toast(
-        description: controller.result?.errorMessage ?? '',
+        description: controller.codeRunner.result?.errorMessage ?? '',
         title: AppLocalizations.of(context)!.runCode,
         type: ToastType.error,
       ),
     );
-    controller.resetError();
+    controller.resetErrorMessageText();
   }
 }
diff --git a/playground/frontend/lib/utils/analytics_utils.dart 
b/playground/frontend/lib/utils/analytics_utils.dart
index 786b54ff382..b5c2ba0dc35 100644
--- a/playground/frontend/lib/utils/analytics_utils.dart
+++ b/playground/frontend/lib/utils/analytics_utils.dart
@@ -20,7 +20,7 @@ import 
'package:playground_components/playground_components.dart';
 
 String getAnalyticsExampleName(PlaygroundController controller) {
   final customCodeName = 'Custom code, sdk ${controller.sdk?.title}';
-  if (controller.isExampleChanged) {
+  if (controller.codeRunner.isExampleChanged) {
     return customCodeName;
   }
   return controller.selectedExample?.path ?? customCodeName;
diff --git a/playground/frontend/playground_components/assets/symbols/go.g.yaml 
b/playground/frontend/playground_components/assets/symbols/go.g.yaml
index 4df541c1a86..cf3b2a0c9c9 100644
--- a/playground/frontend/playground_components/assets/symbols/go.g.yaml
+++ b/playground/frontend/playground_components/assets/symbols/go.g.yaml
@@ -242,6 +242,7 @@
   - Fatalf
   - Fatalln
   - FetchSize
+  - FieldIndexByTag
   - File
   - Flatten
   - FreeDiskSpace
@@ -348,6 +349,7 @@
   - ImpulseValue
   - InboundTagToNode
   - Include
+  - InferFieldNames
   - Info
   - Infof
   - Infoln
@@ -751,6 +753,7 @@
   - SkipK
   - SkipPtr
   - SkipW
+  - SklearnModel
   - Smallest
   - SmallestPerKey
   - Source
@@ -887,11 +890,18 @@
   - Warnf
   - Warnln
   - WindowInto
+  - WithArgs
   - WithContext
   - WithContextf
   - WithExpansionAddr
+  - WithExtraPackages
   - WithIndexes
   - WithQueryLocation
+  - WithReadBucketAuto
+  - WithReadBundleSize
+  - WithReadFilter
+  - WithWriteBatchSize
+  - WithWriteOrdered
   - Wrap
   - WrapIterable
   - WrapMethods
@@ -1401,6 +1411,10 @@ CancelJobResponse:
   - String
   properties:
   - State
+Checkpoint:
+  properties:
+  - Reapply
+  - SR
 Class:
   methods:
   - String
@@ -1668,7 +1682,6 @@ DataSink:
   - UID
 DataSource:
   methods:
-  - Checkpoint
   - Down
   - FinishBundle
   - ID
@@ -1749,6 +1762,25 @@ Discard:
   - Up
   properties:
   - UID
+DiscoverSchemaTransformRequest:
+  methods:
+  - Descriptor
+  - ProtoMessage
+  - ProtoReflect
+  - Reset
+  - String
+DiscoverSchemaTransformResponse:
+  methods:
+  - Descriptor
+  - GetError
+  - GetSchemaTransformConfigs
+  - ProtoMessage
+  - ProtoReflect
+  - Reset
+  - String
+  properties:
+  - Error
+  - SchemaTransformConfigs
 DisplayData:
   methods:
   - Descriptor
@@ -2173,6 +2205,7 @@ ExpansionResponse:
   - Transform
 ExpansionServiceClient:
   methods:
+  - DiscoverSchemaTransform
   - Expand
 ExpansionServiceRunner:
   methods:
@@ -2182,6 +2215,7 @@ ExpansionServiceRunner:
   - String
 ExpansionServiceServer:
   methods:
+  - DiscoverSchemaTransform
   - Expand
 ExternalConfigurationPayload:
   methods:
@@ -3195,6 +3229,7 @@ JobOptions:
   - Region
   - RetainDocker
   - ServiceAccountEmail
+  - Streaming
   - Subnetwork
   - TeardownPolicy
   - TempLocation
@@ -3203,7 +3238,6 @@ JobOptions:
   - Update
   - Worker
   - WorkerHarnessThreads
-  - WorkerJar
   - WorkerRegion
   - WorkerZone
   - Zone
@@ -3381,6 +3415,7 @@ LockRTracker:
   - GetRestriction
   - IsBounded
   - IsDone
+  - String
   - TryClaim
   - TrySplit
   properties:
@@ -4237,6 +4272,10 @@ PlanSnapshot:
 Port:
   properties:
   - URL
+PredictionResult:
+  properties:
+  - Example
+  - Inference
 PrepareJobRequest:
   methods:
   - Descriptor
@@ -4694,6 +4733,11 @@ ReadModifyWriteStateSpec:
   - String
   properties:
   - CoderId
+ReadOption:
+  properties:
+  - BucketAuto
+  - BundleSize
+  - Filter
 ReadOptions:
   properties:
   - IDAttribute
@@ -4947,6 +4991,34 @@ SchemaProvider:
   - BuildDecoder
   - BuildEncoder
   - FromLogicalType
+SchemaTransformConfig:
+  methods:
+  - Descriptor
+  - GetConfigSchema
+  - GetInputPcollectionNames
+  - GetOutputPcollectionNames
+  - ProtoMessage
+  - ProtoReflect
+  - Reset
+  - String
+  properties:
+  - ConfigSchema
+  - InputPcollectionNames
+  - OutputPcollectionNames
+SchemaTransformPayload:
+  methods:
+  - Descriptor
+  - GetConfigurationRow
+  - GetConfigurationSchema
+  - GetIdentifier
+  - ProtoMessage
+  - ProtoReflect
+  - Reset
+  - String
+  properties:
+  - ConfigurationRow
+  - ConfigurationSchema
+  - Identifier
 Scope:
   methods:
   - ID
@@ -5153,6 +5225,7 @@ SplitResult:
   - RI
   - RS
   - TId
+  - Unsuccessful
 SplittableDoFn:
   methods:
   - CreateInitialRestrictionFn
@@ -6016,6 +6089,7 @@ Tracker:
   - GetRestriction
   - IsBounded
   - IsDone
+  - String
   - TryClaim
   - TrySplit
 Transaction:
@@ -6304,7 +6378,6 @@ TypeMismatchError:
   - Error
   properties:
   - Got
-U: {}
 UnimplementedArtifactRetrievalServiceServer:
   methods:
   - GetArtifact
@@ -6334,6 +6407,7 @@ UnimplementedBeamFnWorkerStatusServer:
   - WorkerStatus
 UnimplementedExpansionServiceServer:
   methods:
+  - DiscoverSchemaTransform
   - Expand
 UnimplementedJobServiceServer:
   methods:
@@ -6397,7 +6471,6 @@ UserFn:
 UserStateAdapter:
   methods:
   - NewStateProvider
-V: {}
 Value:
   methods:
   - Clear
@@ -6409,7 +6482,6 @@ Value:
   - Write
   properties:
   - Key
-W: {}
 WallTimeWatermarkEstimator:
   methods:
   - CurrentWatermark
@@ -6560,9 +6632,10 @@ WriteFilesPayload:
   - SideInputs
   - Sink
   - WindowedWrites
+WriteOption:
+  properties:
+  - BatchSize
+  - Ordered
 Writer:
   methods:
   - SaveData
-X: {}
-"Y": {}
-Z: {}
diff --git 
a/playground/frontend/playground_components/assets/symbols/java.g.yaml 
b/playground/frontend/playground_components/assets/symbols/java.g.yaml
index 1fe009d4367..345e11071b9 100644
--- a/playground/frontend/playground_components/assets/symbols/java.g.yaml
+++ b/playground/frontend/playground_components/assets/symbols/java.g.yaml
@@ -474,6 +474,7 @@ AvroIO:
   - withSchema
   - withShardNameTemplate
   - withSuffix
+  - withSyncInterval
   - withTempDirectory
   - withUsesReshuffle
   - withWindowedWrites
@@ -683,9 +684,11 @@ BatchContextImpl:
   methods: 
   - addProperties
   - addTags
+  - asMap
   - createDataset
   - datasetExists
   - discardDataset
+  - get
   - getArguments
   - getDataset
   - getFailureCollector
@@ -703,6 +706,8 @@ BatchContextImpl:
   - getPluginProperties
   - getServiceURL
   - getStageName
+  - has
+  - iterator
   - loadPluginClass
   - newPluginInstance
   - provide
@@ -711,6 +716,10 @@ BatchContextImpl:
   - removeMetadata
   - removeProperties
   - removeTags
+  - set
+  properties: 
+  - DEFAULT_SCHEMA_FIELD_NAME
+  - DEFAULT_SCHEMA_RECORD_NAME
 BatchSinkContextImpl: 
   methods: 
   - addOutput
@@ -1328,11 +1337,13 @@ BeamSqlPipelineOptionsRegistrar:
   - getPipelineOptions
 BeamSqlRelUtils: 
   methods: 
+  - explainLazily
   - getBeamRelInput
   - getErrorRowSchema
   - getInput
   - getNodeStats
   - toPCollection
+  - toString
   properties: 
   - ERROR
   - ROW
@@ -1600,7 +1611,6 @@ BigQueryDirectReadSchemaTransformProvider:
   - buildTransform
   - builder
   - expand
-  - getBigQueryServices
   - getQuery
   - getRowRestriction
   - getSelectedFields
@@ -1791,6 +1801,27 @@ BigQueryStorageTableSource:
   - create
   - getEstimatedSizeBytes
   - populateDisplayData
+BigQueryStorageWriteApiSchemaTransformProvider: 
+  methods: 
+  - build
+  - buildTransform
+  - builder
+  - expand
+  - getCreateDisposition
+  - getTable
+  - getTriggeringFrequencySeconds
+  - getUseAtLeastOnceSemantics
+  - getWriteDisposition
+  - identifier
+  - inputCollectionNames
+  - outputCollectionNames
+  - setBigQueryServices
+  - setCreateDisposition
+  - setTable
+  - setTriggeringFrequencySeconds
+  - setUseAtLeastOnceSemantics
+  - setWriteDisposition
+  - validate
 BigQueryTableProvider: 
   methods: 
   - buildBeamSqlTable
@@ -1935,6 +1966,7 @@ BlockBasedSource:
   - getCurrentRecord
   - getFractionConsumed
   - getFractionOfBlockConsumed
+  - isAtSplitPoint
   - readNextBlock
   - readNextRecord
 BlockingCommitterImpl: 
@@ -2533,6 +2565,8 @@ CdapIO:
   - withKeyClass
   - withLocksDirPath
   - withPluginConfig
+  - withPullFrequencySec
+  - withStartOffset
   - withValueClass
   - write
 ChangeStreamDao: 
@@ -3584,10 +3618,34 @@ DebeziumIO:
   - withFormatFunction
   - withHostName
   - withMaxNumberOfRecords
+  - withMaxTimeToRun
   - withPassword
   - withPort
   - withSourceConnector
   - withUsername
+DebeziumReadSchemaTransformProvider: 
+  methods: 
+  - build
+  - buildTransform
+  - builder
+  - expand
+  - getDatabase
+  - getDebeziumConnectionProperties
+  - getHost
+  - getPassword
+  - getPort
+  - getTable
+  - getUsername
+  - identifier
+  - inputCollectionNames
+  - outputCollectionNames
+  - setDatabase
+  - setDebeziumConnectionProperties
+  - setHost
+  - setPassword
+  - setPort
+  - setTable
+  - setUsername
 DebeziumTransformRegistrar: 
   methods: 
   - buildExternal
@@ -3631,6 +3689,7 @@ DefaultBlobstoreClientBuilderFactory:
   - createBuilder
 DefaultFilenamePolicy: 
   methods: 
+  - constructName
   - decode
   - encode
   - equals
@@ -3996,6 +4055,7 @@ DoFnSignatures:
   - usesBagState
   - usesBundleFinalizer
   - usesMapState
+  - usesMultimapState
   - usesOrderedListState
   - usesSetState
   - usesState
@@ -4863,6 +4923,48 @@ FileSystems:
   - filteredExistingSrcs
   - resultDestinations
   - resultSources
+FileWriteSchemaTransformConfiguration: 
+  methods: 
+  - build
+  - builder
+  - csvConfigurationBuilder
+  - getCharset
+  - getCompression
+  - getCompressionCodecName
+  - getCsvConfiguration
+  - getCsvFormat
+  - getFilenamePrefix
+  - getFilenameSuffix
+  - getFormat
+  - getNumShards
+  - getParquetConfiguration
+  - getPreamble
+  - getRootElement
+  - getRowGroupSize
+  - getShardNameTemplate
+  - getXmlConfiguration
+  - parquetConfigurationBuilder
+  - setCharset
+  - setCompression
+  - setCompressionCodecName
+  - setCsvConfiguration
+  - setCsvFormat
+  - setFilenamePrefix
+  - setFilenameSuffix
+  - setFormat
+  - setNumShards
+  - setParquetConfiguration
+  - setPreamble
+  - setRootElement
+  - setRowGroupSize
+  - setShardNameTemplate
+  - setXmlConfiguration
+  - xmlConfigurationBuilder
+FileWriteSchemaTransformFormatProviders: 
+  methods: 
+  - buildTransform
+  - identifier
+  - loadProviders
 FillGaps: 
   methods: 
   - expand
@@ -5113,6 +5215,7 @@ FnApiStateAccessor:
   - bindCombining
   - bindCombiningWithContext
   - bindMap
+  - bindMultimap
   - bindOrderedList
   - bindSet
   - bindValue
@@ -5495,6 +5598,7 @@ GroupIntoBatches:
   methods: 
   - apply
   - create
+  - createDefault
   - expand
   - getBatchSize
   - getBatchSizeBytes
@@ -5509,8 +5613,11 @@ GroupIntoBatches:
   - onWindowExpiration
   - onWindowTimer
   - processElement
+  - toString
+  - withByteSize
   - withMaxBufferingDuration
   - withShardedKey
+  - withSize
 GrowableOffsetRangeTracker: 
   methods: 
   - getProgress
@@ -6212,6 +6319,36 @@ JdbcIO:
   - withWriteResults
   - write
   - writeVoid
+JdbcReadSchemaTransformProvider: 
+  methods: 
+  - build
+  - buildTransform
+  - builder
+  - expand
+  - getConnectionInitSql
+  - getConnectionProperties
+  - getDriverClassName
+  - getFetchSize
+  - getJdbcUrl
+  - getLocation
+  - getOutputParallelization
+  - getPassword
+  - getReadQuery
+  - getUsername
+  - identifier
+  - inputCollectionNames
+  - outputCollectionNames
+  - setConnectionInitSql
+  - setConnectionProperties
+  - setDriverClassName
+  - setFetchSize
+  - setJdbcUrl
+  - setLocation
+  - setOutputParallelization
+  - setPassword
+  - setReadQuery
+  - setUsername
+  - validate
 JdbcSchemaIOProvider: 
   methods: 
   - buildReader
@@ -6226,6 +6363,34 @@ JdbcSchemaIOProvider:
 JdbcWriteResult: 
   methods: 
   - create
+JdbcWriteSchemaTransformProvider: 
+  methods: 
+  - build
+  - buildTransform
+  - builder
+  - expand
+  - getAutosharding
+  - getConnectionInitSql
+  - getConnectionProperties
+  - getDriverClassName
+  - getJdbcUrl
+  - getLocation
+  - getPassword
+  - getUsername
+  - getWriteStatement
+  - identifier
+  - inputCollectionNames
+  - outputCollectionNames
+  - setAutosharding
+  - setConnectionInitSql
+  - setConnectionProperties
+  - setDriverClassName
+  - setJdbcUrl
+  - setLocation
+  - setPassword
+  - setUsername
+  - setWriteStatement
+  - validate
 JmsIO: 
   methods: 
   - advance
@@ -6370,6 +6535,7 @@ JsonToRow:
 JsonUtils: 
   methods: 
   - apply
+  - beamSchemaFromJsonSchema
   - getJsonBytesToRowFunction
   - getJsonStringToRowFunction
   - getRowToJsonBytesFunction
@@ -6400,6 +6566,12 @@ KafkaCommitOffset:
   methods: 
   - expand
   - processElement
+KafkaConnectUtils: 
+  methods: 
+  - beamRowFromSourceRecordFn
+  - beamSchemaFromKafkaConnectSchema
+  - beamSchemaTypeFromKafkaType
+  - mapSourceRecord
 KafkaIO: 
   methods: 
   - apply
@@ -6490,58 +6662,58 @@ KafkaIO:
   - URN
   - URN_WITHOUT_METADATA
   - URN_WITH_METADATA
-KafkaRecord: 
-  methods: 
-  - equals
-  - getHeaders
-  - getKV
-  - getOffset
-  - getPartition
-  - getTimestamp
-  - getTimestampType
-  - getTopic
-  - hashCode
-KafkaRecordCoder: 
-  methods: 
-  - consistentWithEquals
-  - decode
-  - encode
-  - getCoderArguments
-  - isRegisterByteSizeObserverCheap
-  - of
-  - structuralValue
-  - verifyDeterministic
-KafkaSchemaTransformReadConfiguration: 
+KafkaReadSchemaTransformConfiguration: 
   methods: 
   - build
   - builder
   - getAutoOffsetResetConfig
-  - getAvroSchema
   - getBootstrapServers
   - getConfluentSchemaRegistrySubject
   - getConfluentSchemaRegistryUrl
   - getConsumerConfigUpdates
   - getDataFormat
+  - getSchema
   - getTopic
   - setAutoOffsetResetConfig
-  - setAvroSchema
   - setBootstrapServers
   - setConfluentSchemaRegistrySubject
   - setConfluentSchemaRegistryUrl
   - setConsumerConfigUpdates
   - setDataFormat
+  - setSchema
   - setTopic
   - validate
   properties: 
   - VALID_DATA_FORMATS
   - VALID_START_OFFSET_VALUES
-KafkaSchemaTransformReadProvider: 
+KafkaReadSchemaTransformProvider: 
   methods: 
   - buildTransform
   - expand
   - identifier
   - inputCollectionNames
   - outputCollectionNames
+KafkaRecord: 
+  methods: 
+  - equals
+  - getHeaders
+  - getKV
+  - getOffset
+  - getPartition
+  - getTimestamp
+  - getTimestampType
+  - getTopic
+  - hashCode
+KafkaRecordCoder: 
+  methods: 
+  - consistentWithEquals
+  - decode
+  - encode
+  - getCoderArguments
+  - isRegisterByteSizeObserverCheap
+  - of
+  - structuralValue
+  - verifyDeterministic
 KafkaSourceConsumerFn: 
   methods: 
   - checkDone
@@ -6566,7 +6738,7 @@ KafkaSourceConsumerFn:
   - fetchedRecords
   - history
   - maxRecords
-  - minutesToRun
+  - milisToRun
   - offset
 KafkaSourceDescriptor: 
   methods: 
@@ -6576,6 +6748,25 @@ KafkaTableProvider:
   methods: 
   - buildBeamSqlTable
   - getTableType
+KafkaWriteSchemaTransformProvider: 
+  methods: 
+  - build
+  - buildTransform
+  - builder
+  - expand
+  - getBootstrapServers
+  - getFormat
+  - getProducerConfigUpdates
+  - getTopic
+  - identifier
+  - inputCollectionNames
+  - outputCollectionNames
+  - setBootstrapServers
+  - setFormat
+  - setProducerConfigUpdates
+  - setTopic
+  properties: 
+  - SUPPORTED_FORMATS
 KeyPairUtils: 
   methods: 
   - preparePrivateKey
@@ -6905,9 +7096,7 @@ MapperFactory:
   methods: 
   - changeStreamRecordMapper
   - partitionMetadataMapper
-MappingUtils: 
-  methods: 
-  - registerStreamingPlugin
+MappingUtils: {}
 MatchResult: 
   methods: 
   - build
@@ -7422,6 +7611,7 @@ Neo4jIO:
   - readAll
   - setup
   - startBundle
+  - tearDown
   - withBatchSize
   - withCoder
   - withConfig
@@ -8070,6 +8260,7 @@ ParDo:
   - dispatchBag
   - dispatchCombining
   - dispatchMap
+  - dispatchMultimap
   - dispatchOrderedList
   - dispatchSet
   - dispatchValue
@@ -8422,10 +8613,14 @@ Plugin:
   - getContext
   - getFormatClass
   - getFormatProviderClass
+  - getGetOffsetFn
+  - getGetReceiverArgsFromConfigFn
   - getHadoopConfiguration
   - getPluginClass
   - getPluginConfig
   - getPluginType
+  - getReceiverBuilder
+  - getReceiverClass
   - initContext
   - initPluginType
   - isUnbounded
@@ -8433,8 +8628,11 @@ Plugin:
   - setContext
   - setFormatClass
   - setFormatProviderClass
+  - setGetOffsetFn
+  - setGetReceiverArgsFromConfigFn
   - setPluginClass
   - setPluginType
+  - setReceiverClass
   - withConfig
   - withHadoopConfiguration
 PluginConfigInstantiationUtils: {}
@@ -8684,9 +8882,12 @@ PubsubClient:
   - ackDeadlineSeconds
   - ackId
   - acknowledge
+  - apply
   - createRandomSubscription
+  - createSchema
   - createSubscription
   - createTopic
+  - deleteSchema
   - deleteSubscription
   - deleteTopic
   - equals
@@ -8694,6 +8895,9 @@ PubsubClient:
   - getId
   - getName
   - getPath
+  - getProjectId
+  - getSchema
+  - getSchemaPath
   - hashCode
   - isEOF
   - listSubscriptions
@@ -8707,6 +8911,8 @@ PubsubClient:
   - pull
   - recordId
   - requestTimeMsSinceEpoch
+  - schemaPathFromId
+  - schemaPathFromPath
   - subscriptionPathFromName
   - subscriptionPathFromPath
   - timestampMsSinceEpoch
@@ -8726,11 +8932,15 @@ PubsubGrpcClient:
   - ackDeadlineSeconds
   - acknowledge
   - close
+  - createSchema
   - createSubscription
   - createTopic
+  - deleteSchema
   - deleteSubscription
   - deleteTopic
   - getKind
+  - getSchema
+  - getSchemaPath
   - isEOF
   - listSubscriptions
   - listTopics
@@ -8796,11 +9006,15 @@ PubsubJsonClient:
   - ackDeadlineSeconds
   - acknowledge
   - close
+  - createSchema
   - createSubscription
   - createTopic
+  - deleteSchema
   - deleteSubscription
   - deleteTopic
   - getKind
+  - getSchema
+  - getSchemaPath
   - isEOF
   - listSubscriptions
   - listTopics
@@ -8817,6 +9031,26 @@ PubsubLiteIO:
   - expand
   - read
   - write
+PubsubLiteReadSchemaTransformProvider: 
+  methods: 
+  - build
+  - buildTransform
+  - builder
+  - expand
+  - from
+  - getDataFormat
+  - getLocation
+  - getProject
+  - getSchema
+  - getSubscriptionName
+  - identifier
+  - inputCollectionNames
+  - outputCollectionNames
+  - setDataFormat
+  - setLocation
+  - setProject
+  - setSchema
+  - setSubscriptionName
 PubsubLiteSink: 
   methods: 
   - finishBundle
@@ -8826,6 +9060,25 @@ PubsubLiteTableProvider:
   methods: 
   - buildBeamSqlTable
   - getTableType
+PubsubLiteWriteSchemaTransformProvider: 
+  methods: 
+  - build
+  - buildTransform
+  - builder
+  - expand
+  - getFormat
+  - getLocation
+  - getProject
+  - getTopicName
+  - identifier
+  - inputCollectionNames
+  - outputCollectionNames
+  - setFormat
+  - setLocation
+  - setProject
+  - setTopicName
+  properties: 
+  - SUPPORTED_FORMATS
 PubsubMessage: 
   methods: 
   - equals
@@ -8913,17 +9166,6 @@ PubsubSchemaTransformReadProvider:
   - inputCollectionNames
   - outputCollectionNames
   - validate
-PubsubSchemaTransformWriteConfiguration: 
-  methods: 
-  - build
-  - getFormat
-  - getIdAttribute
-  - getTimestampAttribute
-  - getTopic
-  - setFormat
-  - setIdAttribute
-  - setTimestampAttribute
-  - setTopic
 PubsubTableProvider: 
   methods: 
   - getSchemaIOProvider
@@ -8935,14 +9177,19 @@ PubsubTestClient:
   - advance
   - close
   - createFactoryForCreateSubscription
+  - createFactoryForGetSchema
   - createFactoryForPublish
   - createFactoryForPull
   - createFactoryForPullAndPublish
+  - createSchema
   - createSubscription
   - createTopic
+  - deleteSchema
   - deleteSubscription
   - deleteTopic
   - getKind
+  - getSchema
+  - getSchemaPath
   - isEOF
   - listSubscriptions
   - listTopics
@@ -9006,6 +9253,40 @@ PubsubUnboundedSource:
   - validate
   properties: 
   - outer
+PubsubWriteSchemaTransformConfiguration: 
+  methods: 
+  - build
+  - builder
+  - getAttributesFieldName
+  - getFormat
+  - getIdAttribute
+  - getPayloadFieldName
+  - getSource
+  - getTarget
+  - getTimestampAttributeKey
+  - getTimestampFieldName
+  - getTopic
+  - setAttributesFieldName
+  - setFormat
+  - setIdAttribute
+  - setPayloadFieldName
+  - setSource
+  - setTarget
+  - setTimestampAttributeKey
+  - setTimestampFieldName
+  - setTopic
+  - sourceConfigurationBuilder
+  - targetConfigurationBuilder
+  properties: 
+  - DEFAULT_TIMESTAMP_ATTRIBUTE
+PubsubWriteSchemaTransformProvider: 
+  methods: 
+  - buildTransform
+  - expand
+  - identifier
+  - inputCollectionNames
+  - outputCollectionNames
+  - process
 PulsarIO: 
   methods: 
   - expand
@@ -9330,6 +9611,8 @@ ReadAllViaFileBasedSource:
   - apply
   - expand
   - process
+  properties: 
+  - DEFAULT_USES_RESHUFFLE
 ReadBuilder: 
   methods: 
   - buildExternal
@@ -10180,6 +10463,7 @@ Schema:
   - setOptions
   - setType
   - setUUID
+  - sorted
   - toBuilder
   - toSchema
   - toString
@@ -10542,7 +10826,9 @@ SingleStoreIO:
   - process
   - processElement
   - read
+  - readRows
   - readWithPartitions
+  - readWithPartitionsRows
   - run
   - splitRange
   - withBatchSize
@@ -10558,6 +10844,49 @@ SingleStoreIO:
   - withUserDataMapper
   - withUsername
   - write
+  - writeRows
+SingleStoreSchemaTransformReadConfiguration: 
+  methods: 
+  - build
+  - builder
+  - getDataSourceConfiguration
+  - getOutputParallelization
+  - getQuery
+  - getTable
+  - getWithPartitions
+  - setDataSourceConfiguration
+  - setOutputParallelization
+  - setQuery
+  - setTable
+  - setWithPartitions
+  - toBeamRow
+SingleStoreSchemaTransformReadProvider: 
+  methods: 
+  - buildTransform
+  - expand
+  - identifier
+  - inputCollectionNames
+  - outputCollectionNames
+SingleStoreSchemaTransformWriteConfiguration: 
+  methods: 
+  - build
+  - builder
+  - getBatchSize
+  - getDataSourceConfiguration
+  - getTable
+  - setBatchSize
+  - setDataSourceConfiguration
+  - setTable
+  - toBeamRow
+SingleStoreSchemaTransformWriteProvider: 
+  methods: 
+  - buildTransform
+  - expand
+  - identifier
+  - inputCollectionNames
+  - outputCollectionNames
+  properties: 
+  - INPUT_TAG
 SingleValueCollector: 
   methods: 
   - asContext
@@ -11152,7 +11481,9 @@ SparkReceiverIO:
   - read
   - validateTransform
   - withGetOffsetFn
+  - withPullFrequencySec
   - withSparkReceiverBuilder
+  - withStartOffset
   - withTimestampFn
 Split: 
   methods: 
@@ -11391,10 +11722,12 @@ StateSpecs:
   - hashCode
   - map
   - match
+  - multimap
   - offerCoders
   - orderedList
   - rowBag
   - rowMap
+  - rowMultimap
   - rowOrderedList
   - rowSet
   - rowValue
diff --git 
a/playground/frontend/playground_components/assets/symbols/python.g.yaml 
b/playground/frontend/playground_components/assets/symbols/python.g.yaml
index 01d5afbf2d8..a47447225a6 100644
--- a/playground/frontend/playground_components/assets/symbols/python.g.yaml
+++ b/playground/frontend/playground_components/assets/symbols/python.g.yaml
@@ -899,6 +899,9 @@ BigqueryMatcher:
   methods:
   - describe_mismatch
   - describe_to
+BigQueryMetricsFetcher:
+  methods:
+  - fetch
 BigQueryMetricsPublisher:
   methods:
   - publish
@@ -1273,6 +1276,7 @@ BlobStorageIO:
   - delete_tree
   - exists
   - last_updated
+  - list_files
   - list_prefix
   - open
   - rename
@@ -4824,6 +4828,7 @@ GcsIO:
   - get_project_number
   - kms_key
   - last_updated
+  - list_files
   - list_prefix
   - open
   - rename
@@ -4987,6 +4992,16 @@ GitHubEventsConfig:
   - owner
   - pullRequest
   - push
+GitHubIssueMetaData:
+  properties:
+  - change_point
+  - change_point_timestamp
+  - issue_number
+  - issue_timestamp
+  - issue_url
+  - metric_name
+  - test_id
+  - test_name
 GitRepoSource:
   properties:
   - bitbucketServerConfig
@@ -5795,10 +5810,6 @@ JoinIndex:
 JoinPersonAuctionFn:
   methods:
   - process
-JrhReadPTransformOverride:
-  methods:
-  - get_replacement_transform_for_applied_ptransform
-  - matches
 JsonCoder:
   methods:
   - decode
@@ -5832,6 +5843,7 @@ JustBids:
 JustPerson:
   methods:
   - expand
+KafkaIOTestOptions: {}
 Key:
   methods:
   - from_client_key
@@ -6332,6 +6344,7 @@ MetricsPublisher:
   - publish
 MetricsReader:
   methods:
+  - get_counter_metric
   - publish_metrics
   - publish_values
 MetricStructuredName:
@@ -7877,6 +7890,7 @@ PythonCallable:
   - urn
 PythonCallableWithSource:
   methods:
+  - default_label
   - get_source
   - load_from_expression
   - load_from_fully_qualified_name
@@ -8686,6 +8700,7 @@ S3IO:
   - delete_tree
   - exists
   - last_updated
+  - list_files
   - list_prefix
   - open
   - rename
@@ -10294,6 +10309,18 @@ TestBQJobNames:
   - test_matches_template
   - test_random_in_name
   - test_simple_names
+TestChangePointAnalysis:
+  methods:
+  - setUp
+  - test_alert_on_data_with_change_point
+  - test_alert_on_data_with_reported_change_point
+  - test_change_point_outside_inspection_window_is_not_a_valid_alert
+  - test_duplicate_change_point
+  - test_duplicate_change_points_are_not_valid_alerts
+  - test_edivisive_means
+  - test_is_changepoint_in_valid_window
+  - test_no_alerts_when_no_change_points
+  - test_validate_config
 TestCheckSchemaEqual:
   methods:
   - test_descriptions
@@ -11356,6 +11383,7 @@ TriggerCopyJobs:
   - display_data
   - finish_bundle
   - process
+  - process_one
   - start_bundle
   properties:
   - TRIGGER_DELETE_TEMP_TABLES
diff --git 
a/playground/frontend/playground_components/lib/src/controllers/code_runner.dart
 
b/playground/frontend/playground_components/lib/src/controllers/code_runner.dart
new file mode 100644
index 00000000000..ad2df180d94
--- /dev/null
+++ 
b/playground/frontend/playground_components/lib/src/controllers/code_runner.dart
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+
+import '../../playground_components.dart';
+import '../repositories/models/run_code_request.dart';
+import '../repositories/models/run_code_result.dart';
+import 'snippet_editing_controller.dart';
+
+class CodeRunner extends ChangeNotifier {
+  final CodeRepository? _codeRepository;
+  final ValueGetter<SnippetEditingController> _snippetEditingControllerGetter;
+  SnippetEditingController? snippetEditingController;
+
+  CodeRunner({
+    required ValueGetter<SnippetEditingController>
+        snippetEditingControllerGetter,
+    CodeRepository? codeRepository,
+  })  : _codeRepository = codeRepository,
+        _snippetEditingControllerGetter = snippetEditingControllerGetter;
+
+  RunCodeResult? _result;
+  StreamSubscription<RunCodeResult>? _runSubscription;
+  DateTime? _runStartDate;
+  DateTime? _runStopDate;
+
+  String? get pipelineOptions =>
+      _snippetEditingControllerGetter().pipelineOptions;
+  RunCodeResult? get result => _result;
+  DateTime? get runStartDate => _runStartDate;
+  DateTime? get runStopDate => _runStopDate;
+  bool get isCodeRunning => !(_result?.isFinished ?? true);
+
+  String get resultLog => _result?.log ?? '';
+  String get resultOutput => _result?.output ?? '';
+  String get resultLogOutput => resultLog + resultOutput;
+
+  bool get isExampleChanged {
+    return _snippetEditingControllerGetter().isChanged;
+  }
+
+  void clearResult() {
+    _result = null;
+    notifyListeners();
+  }
+
+  void runCode({void Function()? onFinish}) {
+    _runStartDate = DateTime.now();
+    _runStopDate = null;
+    notifyListeners();
+    snippetEditingController = _snippetEditingControllerGetter();
+
+    final parsedPipelineOptions =
+        parsePipelineOptions(snippetEditingController!.pipelineOptions);
+    if (parsedPipelineOptions == null) {
+      _result = const RunCodeResult(
+        status: RunCodeStatus.compileError,
+        errorMessage: kPipelineOptionsParseError,
+      );
+      _runStopDate = DateTime.now();
+      notifyListeners();
+      return;
+    }
+
+    if (!isExampleChanged &&
+        snippetEditingController!.example?.outputs != null) {
+      unawaited(_showPrecompiledResult());
+    } else {
+      final request = RunCodeRequest(
+        datasets: snippetEditingController?.example?.datasets ?? [],
+        files: snippetEditingController!.getFiles(),
+        sdk: snippetEditingController!.sdk,
+        pipelineOptions: parsedPipelineOptions,
+      );
+      _runSubscription = _codeRepository?.runCode(request).listen((event) {
+        _result = event;
+        notifyListeners();
+
+        if (event.isFinished) {
+          if (onFinish != null) {
+            onFinish();
+          }
+          snippetEditingController = null;
+          _runStopDate = DateTime.now();
+        }
+      });
+      notifyListeners();
+    }
+  }
+
+  /// Resets the error message text so that on the next rebuild
+  /// of `CodeTextAreaWrapper` it is not picked up and not shown as a toast.
+  // TODO: Listen to this object outside of widgets,
+  //  emit toasts from notifications, then remove this method.
+  void resetErrorMessageText() {
+    if (_result == null) {
+      return;
+    }
+    _result = RunCodeResult(
+      status: _result!.status,
+      output: _result!.output,
+    );
+    notifyListeners();
+  }
+
+  Future<void> cancelRun() async {
+    snippetEditingController = null;
+    await _runSubscription?.cancel();
+    final pipelineUuid = _result?.pipelineUuid ?? '';
+
+    if (pipelineUuid.isNotEmpty) {
+      await _codeRepository?.cancelExecution(pipelineUuid);
+    }
+
+    _result = RunCodeResult(
+      status: RunCodeStatus.finished,
+      output: _result?.output,
+      log: (_result?.log ?? '') + kExecutionCancelledText,
+      graph: _result?.graph,
+    );
+
+    _runStopDate = DateTime.now();
+    notifyListeners();
+  }
+
+  Future<void> _showPrecompiledResult() async {
+    _result = const RunCodeResult(
+      status: RunCodeStatus.preparation,
+    );
+    final selectedExample = snippetEditingController!.example!;
+
+    notifyListeners();
+    // add a little delay to improve user experience
+    await Future.delayed(kPrecompiledDelay);
+
+    final String logs = selectedExample.logs ?? '';
+    _result = RunCodeResult(
+      status: RunCodeStatus.finished,
+      output: selectedExample.outputs,
+      log: kCachedResultsLog + logs,
+      graph: selectedExample.graph,
+    );
+
+    _runStopDate = DateTime.now();
+    notifyListeners();
+  }
+}
diff --git 
a/playground/frontend/playground_components/lib/src/models/outputs.dart 
b/playground/frontend/playground_components/lib/src/controllers/output_filter_type_controller.dart
similarity index 73%
copy from playground/frontend/playground_components/lib/src/models/outputs.dart
copy to 
playground/frontend/playground_components/lib/src/controllers/output_filter_type_controller.dart
index fa1a360063c..d245e800b01 100644
--- a/playground/frontend/playground_components/lib/src/models/outputs.dart
+++ 
b/playground/frontend/playground_components/lib/src/controllers/output_filter_type_controller.dart
@@ -16,21 +16,15 @@
  * limitations under the License.
  */
 
-enum OutputType {
-  all,
-  log,
-  output,
-  graph,
-}
+import 'package:flutter/material.dart';
+
+import '../../playground_components.dart';
 
-class Outputs {
-  final String output;
-  final String graph;
-  final String log;
+class OutputFilterTypeController extends ChangeNotifier {
+  OutputType outputFilterType = OutputType.all;
 
-  const Outputs({
-    required this.output,
-    required this.graph,
-    required this.log,
-  });
+  void setOutputFilterType(OutputType type) {
+    outputFilterType = type;
+    notifyListeners();
+  }
 }
diff --git 
a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart
 
b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart
index 86aa44f86b9..de8ee9112ed 100644
--- 
a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart
+++ 
b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart
@@ -32,16 +32,14 @@ import 
'../models/example_loading_descriptors/examples_loading_descriptor.dart';
 import 
'../models/example_loading_descriptors/standard_example_loading_descriptor.dart';
 import 
'../models/example_loading_descriptors/user_shared_example_loading_descriptor.dart';
 import '../models/intents.dart';
-import '../models/outputs.dart';
 import '../models/sdk.dart';
 import '../models/shortcut.dart';
 import '../repositories/code_repository.dart';
-import '../repositories/models/run_code_request.dart';
-import '../repositories/models/run_code_result.dart';
 import '../services/symbols/loaders/map.dart';
 import '../services/symbols/symbols_notifier.dart';
-import '../util/pipeline_options.dart';
+import 'code_runner.dart';
 import 'example_loaders/examples_loader.dart';
+import 'output_filter_type_controller.dart';
 import 'snippet_editing_controller.dart';
 
 const kTitleLength = 25;
@@ -58,26 +56,26 @@ const kCachedResultsLog =
 class PlaygroundController with ChangeNotifier {
   final ExampleCache exampleCache;
   final ExamplesLoader examplesLoader;
+  final OutputFilterTypeController outputTypeController =
+      OutputFilterTypeController();
+
+  late final CodeRunner codeRunner;
 
   final _snippetEditingControllers = <Sdk, SnippetEditingController>{};
 
   Sdk? _sdk;
-  final CodeRepository? _codeRepository;
-
-  RunCodeResult? _result;
-  StreamSubscription<RunCodeResult>? _runSubscription;
-  StreamController<int>? _executionTime;
-
-  // TODO(alexeyinkin): Extract along with run status, 
https://github.com/apache/beam/issues/23248
-  OutputType selectedOutputFilterType = OutputType.all;
-  String outputResult = '';
 
   PlaygroundController({
     required this.exampleCache,
     required this.examplesLoader,
     CodeRepository? codeRepository,
-  }) : _codeRepository = codeRepository {
+  }) {
     examplesLoader.setPlaygroundController(this);
+
+    codeRunner = CodeRunner(
+      codeRepository: codeRepository,
+      snippetEditingControllerGetter: requireSnippetEditingController,
+    )..addListener(notifyListeners);
   }
 
   SnippetEditingController _getOrCreateSnippetEditingController(
@@ -127,18 +125,6 @@ class PlaygroundController with ChangeNotifier {
   String? get source =>
       snippetEditingController?.activeFileController?.codeController.fullText;
 
-  bool get isCodeRunning => !(result?.isFinished ?? true);
-
-  RunCodeResult? get result => _result;
-
-  String? get pipelineOptions => snippetEditingController?.pipelineOptions;
-
-  Stream<int>? get executionTime => _executionTime?.stream;
-
-  bool get isExampleChanged {
-    return snippetEditingController?.isChanged ?? false;
-  }
-
   // TODO(alexeyinkin): Single source of truth for whether graph is supported, 
https://github.com/apache/beam/issues/23251
   bool get graphAvailable =>
       selectedExample?.type != ExampleType.test &&
@@ -232,9 +218,7 @@ class PlaygroundController with ChangeNotifier {
       controller.setExample(example, descriptor: descriptor);
     }
 
-    _result = null;
-    _executionTime = null;
-    setOutputResult('');
+    codeRunner.clearResult();
     notifyListeners();
   }
 
@@ -265,34 +249,14 @@ class PlaygroundController with ChangeNotifier {
     GetIt.instance.get<SymbolsNotifier>().addLoaderIfNot(mode, loader);
   }
 
-  void setSelectedOutputFilterType(OutputType type) {
-    selectedOutputFilterType = type;
-    notifyListeners();
-  }
-
-  void setOutputResult(String outputs) {
-    outputResult = outputs;
-    notifyListeners();
-  }
-
-  void clearOutput() {
-    _result = null;
-    notifyListeners();
-  }
-
-  void reset() {
+  Future<void> reset() async {
+    await codeRunner.cancelRun();
     snippetEditingController?.reset();
-    _executionTime = null;
-    outputResult = '';
-    notifyListeners();
+    codeRunner.clearResult();
   }
 
-  void resetError() {
-    if (result == null) {
-      return;
-    }
-    _result = RunCodeResult(status: result!.status, output: result!.output);
-    notifyListeners();
+  void resetErrorMessageText() {
+    codeRunner.resetErrorMessageText();
   }
 
   void setPipelineOptions(String options) {
@@ -301,136 +265,6 @@ class PlaygroundController with ChangeNotifier {
     notifyListeners();
   }
 
-  void runCode({void Function()? onFinish}) {
-    final controller = requireSnippetEditingController();
-    final parsedPipelineOptions =
-        parsePipelineOptions(controller.pipelineOptions);
-    if (parsedPipelineOptions == null) {
-      _result = const RunCodeResult(
-        status: RunCodeStatus.compileError,
-        errorMessage: kPipelineOptionsParseError,
-      );
-      notifyListeners();
-      return;
-    }
-    _executionTime?.close();
-    _executionTime = _createExecutionTimeStream();
-    if (!isExampleChanged && controller.example?.outputs != null) {
-      _showPrecompiledResult(controller);
-    } else {
-      final request = RunCodeRequest(
-        datasets: selectedExample?.datasets ?? [],
-        files: controller.getFiles(),
-        pipelineOptions: parsedPipelineOptions,
-        sdk: controller.sdk,
-      );
-      _runSubscription = _codeRepository?.runCode(request).listen((event) {
-        _result = event;
-        filterOutput(selectedOutputFilterType);
-
-        if (event.isFinished && onFinish != null) {
-          onFinish();
-          _executionTime?.close();
-        }
-        notifyListeners();
-      });
-      notifyListeners();
-    }
-  }
-
-  Future<void> cancelRun() async {
-    await _runSubscription?.cancel();
-    final pipelineUuid = result?.pipelineUuid ?? '';
-
-    if (pipelineUuid.isNotEmpty) {
-      await _codeRepository?.cancelExecution(pipelineUuid);
-    }
-
-    _result = RunCodeResult(
-      status: RunCodeStatus.finished,
-      output: _result?.output,
-      log: (_result?.log ?? '') + kExecutionCancelledText,
-      graph: _result?.graph,
-    );
-
-    final log = _result?.log ?? '';
-    final output = _result?.output ?? '';
-    setOutputResult(log + output);
-    await _executionTime?.close();
-    notifyListeners();
-  }
-
-  Future<void> _showPrecompiledResult(
-    SnippetEditingController snippetEditingController,
-  ) async {
-    _result = const RunCodeResult(
-      status: RunCodeStatus.preparation,
-    );
-    final selectedExample = snippetEditingController.example!;
-
-    notifyListeners();
-    // add a little delay to improve user experience
-    await Future.delayed(kPrecompiledDelay);
-
-    String logs = selectedExample.logs ?? '';
-    _result = RunCodeResult(
-      status: RunCodeStatus.finished,
-      output: selectedExample.outputs,
-      log: kCachedResultsLog + logs,
-      graph: selectedExample.graph,
-    );
-
-    filterOutput(selectedOutputFilterType);
-    await _executionTime?.close();
-    notifyListeners();
-  }
-
-  StreamController<int> _createExecutionTimeStream() {
-    StreamController<int>? streamController;
-    Timer? timer;
-    Duration timerInterval = const Duration(milliseconds: 
kExecutionTimeUpdate);
-    int ms = 0;
-
-    void stopTimer() {
-      timer?.cancel();
-      streamController?.close();
-    }
-
-    void tick(_) {
-      ms += kExecutionTimeUpdate;
-      streamController?.add(ms);
-    }
-
-    void startTimer() {
-      timer = Timer.periodic(timerInterval, tick);
-    }
-
-    streamController = StreamController<int>.broadcast(
-      onListen: startTimer,
-      onCancel: stopTimer,
-    );
-
-    return streamController;
-  }
-
-  void filterOutput(OutputType type) {
-    final output = result?.output ?? '';
-    final log = result?.log ?? '';
-
-    switch (type) {
-      case OutputType.all:
-      case OutputType.graph:
-        setOutputResult(log + output);
-        break;
-      case OutputType.log:
-        setOutputResult(log);
-        break;
-      case OutputType.output:
-        setOutputResult(output);
-        break;
-    }
-  }
-
   Future<UserSharedExampleLoadingDescriptor> saveSnippet() async {
     final snippetController = requireSnippetEditingController();
     final files = snippetController.getFiles();
@@ -480,7 +314,7 @@ class PlaygroundController with ChangeNotifier {
     ),
     actionIntent: const RunIntent(),
     createAction: (BuildContext context) => CallbackAction(
-      onInvoke: (_) => runCode(),
+      onInvoke: (_) => codeRunner.runCode(),
     ),
   );
 
@@ -500,4 +334,10 @@ class PlaygroundController with ChangeNotifier {
         runShortcut,
         resetShortcut,
       ];
+
+  @override
+  void dispose() {
+    super.dispose();
+    codeRunner.removeListener(notifyListeners);
+  }
 }
diff --git 
a/playground/frontend/playground_components/lib/src/models/outputs.dart 
b/playground/frontend/playground_components/lib/src/models/outputs.dart
index fa1a360063c..4d56e19b217 100644
--- a/playground/frontend/playground_components/lib/src/models/outputs.dart
+++ b/playground/frontend/playground_components/lib/src/models/outputs.dart
@@ -20,7 +20,6 @@ enum OutputType {
   all,
   log,
   output,
-  graph,
 }
 
 class Outputs {
diff --git 
a/playground/frontend/playground_components/lib/src/widgets/output/output_area.dart
 
b/playground/frontend/playground_components/lib/src/widgets/output/output_area.dart
index f1bf48b2c26..d0ac762f858 100644
--- 
a/playground/frontend/playground_components/lib/src/widgets/output/output_area.dart
+++ 
b/playground/frontend/playground_components/lib/src/widgets/output/output_area.dart
@@ -17,8 +17,9 @@
  */
 
 import 'package:flutter/material.dart';
-import 'package:playground_components/playground_components.dart';
 
+import '../../controllers/playground_controller.dart';
+import '../../models/outputs.dart';
 import 'graph/graph.dart';
 import 'output_result.dart';
 
@@ -28,35 +29,52 @@ class OutputArea extends StatelessWidget {
   final Axis graphDirection;
 
   const OutputArea({
-    Key? key,
+    super.key,
     required this.playgroundController,
     required this.tabController,
     required this.graphDirection,
-  }) : super(key: key);
+  });
+
+  String _getResultOutput() {
+    final outputType =
+        playgroundController.outputTypeController.outputFilterType;
+    switch (outputType) {
+      case OutputType.log:
+        return playgroundController.codeRunner.resultLog;
+      case OutputType.output:
+        return playgroundController.codeRunner.resultOutput;
+      case OutputType.all:
+        return playgroundController.codeRunner.resultLogOutput;
+    }
+  }
 
   @override
   Widget build(BuildContext context) {
     final sdk = playgroundController.sdk;
 
-    return Container(
-      color: Theme.of(context).backgroundColor,
-      child: TabBarView(
-        controller: tabController,
-        physics: const NeverScrollableScrollPhysics(),
-        children: <Widget>[
-          OutputResult(
-            text: playgroundController.outputResult,
-            isSelected: tabController.index == 0,
-          ),
-          if (playgroundController.graphAvailable)
-            sdk == null
-                ? Container()
-                : GraphTab(
-                    graph: playgroundController.result?.graph ?? '',
-                    sdk: sdk,
-                    direction: graphDirection,
-                  ),
-        ],
+    return AnimatedBuilder(
+      animation: playgroundController.outputTypeController,
+      builder: (context, child) => ColoredBox(
+        color: Theme.of(context).backgroundColor,
+        child: TabBarView(
+          controller: tabController,
+          physics: const NeverScrollableScrollPhysics(),
+          children: <Widget>[
+            OutputResult(
+              text: _getResultOutput(),
+              isSelected: tabController.index == 0,
+            ),
+            if (playgroundController.graphAvailable)
+              sdk == null
+                  ? Container()
+                  : GraphTab(
+                      graph:
+                          playgroundController.codeRunner.result?.graph ?? '',
+                      sdk: sdk,
+                      direction: graphDirection,
+                    ),
+          ],
+        ),
       ),
     );
   }
diff --git 
a/playground/frontend/playground_components/lib/src/widgets/output/output_tab.dart
 
b/playground/frontend/playground_components/lib/src/widgets/output/output_tab.dart
index 326b1c72448..2fb40e9ecd1 100644
--- 
a/playground/frontend/playground_components/lib/src/widgets/output/output_tab.dart
+++ 
b/playground/frontend/playground_components/lib/src/widgets/output/output_tab.dart
@@ -27,7 +27,9 @@ class OutputTab extends StatefulWidget {
   final PlaygroundController playgroundController;
   final String name;
   final bool isSelected;
-  final String value;
+
+  /// Used to check if an update marker should be added on the tab
+  final String maxPossibleContent;
   final bool hasFilter;
 
   const OutputTab({
@@ -35,7 +37,7 @@ class OutputTab extends StatefulWidget {
     required this.playgroundController,
     required this.name,
     required this.isSelected,
-    required this.value,
+    required this.maxPossibleContent,
     this.hasFilter = false,
   });
 
@@ -53,8 +55,8 @@ class _OutputTabState extends State<OutputTab> {
         hasNewContent = false;
       });
     } else if (!widget.isSelected &&
-        widget.value.isNotEmpty &&
-        oldWidget.value != widget.value) {
+        widget.maxPossibleContent.isNotEmpty &&
+        oldWidget.maxPossibleContent != widget.maxPossibleContent) {
       setState(() {
         hasNewContent = true;
       });
diff --git 
a/playground/frontend/playground_components/lib/src/widgets/output/output_tabs.dart
 
b/playground/frontend/playground_components/lib/src/widgets/output/output_tabs.dart
index 80839296fbf..f36a6389fc2 100644
--- 
a/playground/frontend/playground_components/lib/src/widgets/output/output_tabs.dart
+++ 
b/playground/frontend/playground_components/lib/src/widgets/output/output_tabs.dart
@@ -43,7 +43,7 @@ class OutputTabs extends StatelessWidget {
             playgroundController: playgroundController,
             name: 'widgets.output.result'.tr(),
             isSelected: tabController.index == 0,
-            value: playgroundController.outputResult,
+            maxPossibleContent: 
playgroundController.codeRunner.resultLogOutput,
             hasFilter: true,
           ),
           if (playgroundController.graphAvailable)
@@ -51,7 +51,8 @@ class OutputTabs extends StatelessWidget {
               playgroundController: playgroundController,
               name: 'widgets.output.graph'.tr(),
               isSelected: tabController.index == 2,
-              value: playgroundController.result?.graph ?? '',
+              maxPossibleContent:
+                  playgroundController.codeRunner.result?.graph ?? '',
             ),
         ],
       ),
diff --git 
a/playground/frontend/playground_components/lib/src/widgets/output/result_filter_bubble.dart
 
b/playground/frontend/playground_components/lib/src/widgets/output/result_filter_bubble.dart
index a18ddb01d81..2a93919e0e5 100644
--- 
a/playground/frontend/playground_components/lib/src/widgets/output/result_filter_bubble.dart
+++ 
b/playground/frontend/playground_components/lib/src/widgets/output/result_filter_bubble.dart
@@ -36,14 +36,14 @@ class ResultFilterBubble extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    final isSelected = type == playgroundController.selectedOutputFilterType;
+    final isSelected =
+        type == playgroundController.outputTypeController.outputFilterType;
 
     return BubbleWidget(
       isSelected: isSelected,
       onTap: () {
         if (!isSelected) {
-          playgroundController.setSelectedOutputFilterType(type);
-          playgroundController.filterOutput(type);
+          playgroundController.outputTypeController.setOutputFilterType(type);
         }
       },
       title: name,
diff --git 
a/playground/frontend/playground_components/lib/src/widgets/output/result_filter_popover.dart
 
b/playground/frontend/playground_components/lib/src/widgets/output/result_filter_popover.dart
index a74c67e2fa1..671f7c56613 100644
--- 
a/playground/frontend/playground_components/lib/src/widgets/output/result_filter_popover.dart
+++ 
b/playground/frontend/playground_components/lib/src/widgets/output/result_filter_popover.dart
@@ -52,7 +52,7 @@ class ResultFilterPopover extends StatelessWidget {
                     vertical: BeamSizes.size4,
                   ),
                   child: AnimatedBuilder(
-                    animation: playgroundController,
+                    animation: playgroundController.outputTypeController,
                     builder: (context, child) => Row(
                       children: [
                         ResultFilterBubble(
diff --git 
a/playground/frontend/lib/pages/standalone_playground/widgets/close_listener.dart
 
b/playground/frontend/playground_components/lib/src/widgets/periodic_builder.dart
similarity index 60%
copy from 
playground/frontend/lib/pages/standalone_playground/widgets/close_listener.dart
copy to 
playground/frontend/playground_components/lib/src/widgets/periodic_builder.dart
index ba2a19a7ae2..aed7d8c21b8 100644
--- 
a/playground/frontend/lib/pages/standalone_playground/widgets/close_listener.dart
+++ 
b/playground/frontend/playground_components/lib/src/widgets/periodic_builder.dart
@@ -16,34 +16,46 @@
  * limitations under the License.
  */
 
-import 'package:flutter/material.dart';
-import 'package:playground_components/playground_components.dart';
-import 'dart:html' as html;
+import 'dart:async';
 
-import 'package:provider/provider.dart';
+import 'package:flutter/material.dart';
 
-class CloseListener extends StatefulWidget {
-  final Widget child;
+class PeriodicBuilderWidget extends StatefulWidget {
+  final Duration interval;
+  final ValueGetter<Widget> builder;
 
-  const CloseListener({Key? key, required this.child}) : super(key: key);
+  const PeriodicBuilderWidget({
+    super.key,
+    required this.interval,
+    required this.builder,
+  });
 
   @override
-  State<CloseListener> createState() => _CloseListenerState();
+  State<PeriodicBuilderWidget> createState() => _PeriodicBuilderWidgetState();
 }
 
-class _CloseListenerState extends State<CloseListener> {
+class _PeriodicBuilderWidgetState extends State<PeriodicBuilderWidget> {
+  late Timer _timer;
+
   @override
   void initState() {
-    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
-      html.window.onBeforeUnload.listen((event) async {
-        Provider.of<PlaygroundController>(context, listen: false).cancelRun();
-      });
-    });
     super.initState();
+    _timer = Timer.periodic(
+      widget.interval,
+      (_) {
+        setState(() {});
+      },
+    );
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+    _timer.cancel();
   }
 
   @override
   Widget build(BuildContext context) {
-    return widget.child;
+    return widget.builder();
   }
 }
diff --git 
a/playground/frontend/playground_components/lib/src/widgets/run_button.dart 
b/playground/frontend/playground_components/lib/src/widgets/run_button.dart
index 66ac0224797..c3cdaea4553 100644
--- a/playground/frontend/playground_components/lib/src/widgets/run_button.dart
+++ b/playground/frontend/playground_components/lib/src/widgets/run_button.dart
@@ -22,76 +22,118 @@ import 'package:flutter/material.dart';
 import '../constants/sizes.dart';
 import '../controllers/playground_controller.dart';
 import '../theme/theme.dart';
+import 'periodic_builder.dart';
 import 'shortcut_tooltip.dart';
 
-const kMsToSec = 1000;
-const kSecondsFractions = 1;
-
-const _width = 150.0;
-
 class RunButton extends StatelessWidget {
   final PlaygroundController playgroundController;
-  final bool isRunning;
   final VoidCallback runCode;
   final VoidCallback cancelRun;
-  final bool disabled;
+  final bool isEnabled;
 
   const RunButton({
     super.key,
     required this.playgroundController,
-    required this.isRunning,
     required this.runCode,
     required this.cancelRun,
-    this.disabled = false,
+    this.isEnabled = true,
   });
 
+  static const _buttonTextRebuildInterval = 100;
+  static const _width = 150.0;
+
   @override
   Widget build(BuildContext context) {
-    return SizedBox(
-      width: _width,
-      height: BeamSizes.buttonHeight,
-      child: ShortcutTooltip(
-        shortcut: playgroundController.runShortcut,
-        child: ElevatedButton.icon(
-          style: const ButtonStyle(
-            padding: MaterialStatePropertyAll(EdgeInsets.zero),
+    return AnimatedBuilder(
+      animation: playgroundController.codeRunner,
+      builder: (context, child) {
+        final isRunning = playgroundController.codeRunner.isCodeRunning;
+        final runStartDate = playgroundController.codeRunner.runStartDate;
+        final runStopDate = playgroundController.codeRunner.runStopDate;
+
+        return SizedBox(
+          width: _width,
+          height: BeamSizes.buttonHeight,
+          child: ShortcutTooltip(
+            shortcut: playgroundController.runShortcut,
+            child: ElevatedButton.icon(
+              style: const ButtonStyle(
+                padding: MaterialStatePropertyAll(EdgeInsets.zero),
+              ),
+              icon: isRunning
+                  ? SizedBox(
+                      width: BeamIconSizes.small,
+                      height: BeamIconSizes.small,
+                      child: CircularProgressIndicator(
+                        color: Theme.of(context)
+                            .extension<BeamThemeExtension>()!
+                            .primaryBackgroundTextColor,
+                      ),
+                    )
+                  : const Icon(Icons.play_arrow),
+              label: isRunning
+                  // TODO(nausharipov): fix bug
+                  // It is also rebuilt on every codeRunner notification
+                  ? PeriodicBuilderWidget(
+                      interval: const Duration(
+                        milliseconds: _buttonTextRebuildInterval,
+                      ),
+                      builder: () {
+                        return _ButtonText(
+                          isRunning: isRunning,
+                          runStartDate: runStartDate,
+                          runStopDate: runStopDate,
+                        );
+                      },
+                    )
+                  : _ButtonText(
+                      isRunning: isRunning,
+                      runStartDate: runStartDate,
+                      runStopDate: runStopDate,
+                    ),
+              onPressed: isEnabled ? _onPressed() : null,
+            ),
           ),
-          icon: isRunning
-              ? SizedBox(
-                  width: BeamIconSizes.small,
-                  height: BeamIconSizes.small,
-                  child: CircularProgressIndicator(
-                    color: Theme.of(context)
-                        .extension<BeamThemeExtension>()!
-                        .primaryBackgroundTextColor,
-                  ),
-                )
-              : const Icon(Icons.play_arrow),
-          label: StreamBuilder(
-              stream: playgroundController.executionTime,
-              builder: (context, AsyncSnapshot<int> state) {
-                final seconds = (state.data ?? 0) / kMsToSec;
-                final runText = 'widgets.runOrCancelButton.titles.run'.tr();
-                final cancelText =
-                    'widgets.runOrCancelButton.titles.cancel'.tr();
-                final buttonText = isRunning ? cancelText : runText;
-                if (seconds > 0) {
-                  return Text(
-                    '$buttonText 
(${seconds.toStringAsFixed(kSecondsFractions)} s)',
-                  );
-                }
-                return Text(buttonText);
-              }),
-          onPressed: onPressHandler(),
-        ),
-      ),
+        );
+      },
     );
   }
 
-  onPressHandler() {
-    if (disabled) {
-      return null;
+  VoidCallback _onPressed() {
+    return playgroundController.codeRunner.isCodeRunning ? cancelRun : runCode;
+  }
+}
+
+class _ButtonText extends StatelessWidget {
+  final bool isRunning;
+  final DateTime? runStartDate;
+  final DateTime? runStopDate;
+
+  const _ButtonText({
+    required this.isRunning,
+    required this.runStartDate,
+    required this.runStopDate,
+  });
+
+  static const _msToSec = 1000;
+  static const _secondsFractionDigits = 1;
+
+  @override
+  Widget build(BuildContext context) {
+    final runText = 'widgets.runOrCancelButton.titles.run'.tr();
+    final cancelText = 'widgets.runOrCancelButton.titles.cancel'.tr();
+    final buttonText = isRunning ? cancelText : runText;
+    final runStopDateOrNow = runStopDate ?? DateTime.now();
+
+    final elapsedDuration =
+        runStopDateOrNow.difference(runStartDate ?? DateTime.now());
+
+    if (elapsedDuration.inMilliseconds > 0) {
+      final seconds = elapsedDuration.inMilliseconds / _msToSec;
+      return Text(
+        '$buttonText (${seconds.toStringAsFixed(_secondsFractionDigits)} s)',
+      );
     }
-    return !isRunning ? runCode : cancelRun;
+    return Text(buttonText);
   }
 }
diff --git 
a/playground/frontend/playground_components/lib/src/widgets/run_or_cancel_button.dart
 
b/playground/frontend/playground_components/lib/src/widgets/run_or_cancel_button.dart
index 04b6b911f76..75ebf74c7f9 100644
--- 
a/playground/frontend/playground_components/lib/src/widgets/run_or_cancel_button.dart
+++ 
b/playground/frontend/playground_components/lib/src/widgets/run_or_cancel_button.dart
@@ -42,16 +42,16 @@ class RunOrCancelButton extends StatelessWidget {
   Widget build(BuildContext context) {
     return RunButton(
       playgroundController: playgroundController,
-      isRunning: playgroundController.isCodeRunning,
-      cancelRun: () {
+      isEnabled: !(playgroundController.selectedExample?.isMultiFile ?? false),
+      cancelRun: () async {
         beforeCancel?.call();
-        playgroundController.cancelRun().catchError(
+        await playgroundController.codeRunner.cancelRun().catchError(
               (_) => PlaygroundComponents.toastNotifier.add(_getErrorToast()),
             );
       },
       runCode: () {
         beforeRun?.call();
-        playgroundController.runCode(
+        playgroundController.codeRunner.runCode(
           onFinish: onComplete,
         );
       },
diff --git 
a/playground/frontend/playground_components/test/src/controllers/example_loaders/examples_loader_test.mocks.dart
 
b/playground/frontend/playground_components/test/src/controllers/example_loaders/examples_loader_test.mocks.dart
index 6ac39341a84..35b0ac0a006 100644
--- 
a/playground/frontend/playground_components/test/src/controllers/example_loaders/examples_loader_test.mocks.dart
+++ 
b/playground/frontend/playground_components/test/src/controllers/example_loaders/examples_loader_test.mocks.dart
@@ -4,31 +4,33 @@
 
 // ignore_for_file: no_leading_underscores_for_library_prefixes
 import 'dart:async' as _i14;
-import 'dart:ui' as _i15;
+import 'dart:ui' as _i16;
 
 import 'package:mockito/mockito.dart' as _i1;
 import 'package:playground_components/src/cache/example_cache.dart' as _i2;
+import 'package:playground_components/src/controllers/code_runner.dart' as _i5;
 import 
'package:playground_components/src/controllers/example_loaders/examples_loader.dart'
     as _i3;
+import 
'package:playground_components/src/controllers/output_filter_type_controller.dart'
+    as _i4;
 import 
'package:playground_components/src/controllers/playground_controller.dart'
-    as _i10;
+    as _i12;
 import 
'package:playground_components/src/controllers/snippet_editing_controller.dart'
-    as _i5;
+    as _i7;
 import 'package:playground_components/src/models/category_with_examples.dart'
-    as _i16;
-import 'package:playground_components/src/models/example.dart' as _i9;
-import 'package:playground_components/src/models/example_base.dart' as _i8;
+    as _i17;
+import 'package:playground_components/src/models/example.dart' as _i11;
+import 'package:playground_components/src/models/example_base.dart' as _i10;
 import 
'package:playground_components/src/models/example_loading_descriptors/example_loading_descriptor.dart'
-    as _i13;
+    as _i15;
 import 
'package:playground_components/src/models/example_loading_descriptors/examples_loading_descriptor.dart'
-    as _i7;
+    as _i9;
 import 
'package:playground_components/src/models/example_loading_descriptors/user_shared_example_loading_descriptor.dart'
-    as _i6;
-import 'package:playground_components/src/models/loading_status.dart' as _i17;
-import 'package:playground_components/src/models/outputs.dart' as _i11;
-import 'package:playground_components/src/models/sdk.dart' as _i12;
-import 'package:playground_components/src/models/shortcut.dart' as _i4;
-import 'package:playground_components/src/models/snippet_file.dart' as _i18;
+    as _i8;
+import 'package:playground_components/src/models/loading_status.dart' as _i18;
+import 'package:playground_components/src/models/sdk.dart' as _i13;
+import 'package:playground_components/src/models/shortcut.dart' as _i6;
+import 'package:playground_components/src/models/snippet_file.dart' as _i19;
 
 // ignore_for_file: type=lint
 // ignore_for_file: avoid_redundant_argument_values
@@ -44,26 +46,31 @@ class _FakeExampleCache_0 extends _i1.Fake implements 
_i2.ExampleCache {}
 
 class _FakeExamplesLoader_1 extends _i1.Fake implements _i3.ExamplesLoader {}
 
-class _FakeBeamShortcut_2 extends _i1.Fake implements _i4.BeamShortcut {}
+class _FakeOutputFilterTypeController_2 extends _i1.Fake
+    implements _i4.OutputFilterTypeController {}
+
+class _FakeCodeRunner_3 extends _i1.Fake implements _i5.CodeRunner {}
 
-class _FakeSnippetEditingController_3 extends _i1.Fake
-    implements _i5.SnippetEditingController {}
+class _FakeBeamShortcut_4 extends _i1.Fake implements _i6.BeamShortcut {}
 
-class _FakeUserSharedExampleLoadingDescriptor_4 extends _i1.Fake
-    implements _i6.UserSharedExampleLoadingDescriptor {}
+class _FakeSnippetEditingController_5 extends _i1.Fake
+    implements _i7.SnippetEditingController {}
 
-class _FakeExamplesLoadingDescriptor_5 extends _i1.Fake
-    implements _i7.ExamplesLoadingDescriptor {}
+class _FakeUserSharedExampleLoadingDescriptor_6 extends _i1.Fake
+    implements _i8.UserSharedExampleLoadingDescriptor {}
 
-class _FakeExampleBase_6 extends _i1.Fake implements _i8.ExampleBase {}
+class _FakeExamplesLoadingDescriptor_7 extends _i1.Fake
+    implements _i9.ExamplesLoadingDescriptor {}
 
-class _FakeExample_7 extends _i1.Fake implements _i9.Example {}
+class _FakeExampleBase_8 extends _i1.Fake implements _i10.ExampleBase {}
+
+class _FakeExample_9 extends _i1.Fake implements _i11.Example {}
 
 /// A class which mocks [PlaygroundController].
 ///
 /// See the documentation for Mockito's code generation for more information.
 class MockPlaygroundController extends _i1.Mock
-    implements _i10.PlaygroundController {
+    implements _i12.PlaygroundController {
   MockPlaygroundController() {
     _i1.throwOnMissingStub(this);
   }
@@ -79,39 +86,31 @@ class MockPlaygroundController extends _i1.Mock
         returnValue: _FakeExamplesLoader_1(),
       ) as _i3.ExamplesLoader);
   @override
-  _i11.OutputType get selectedOutputFilterType => (super.noSuchMethod(
-        Invocation.getter(#selectedOutputFilterType),
-        returnValue: _i11.OutputType.all,
-      ) as _i11.OutputType);
-  @override
-  set selectedOutputFilterType(_i11.OutputType? _selectedOutputFilterType) =>
-      super.noSuchMethod(
-        Invocation.setter(
-          #selectedOutputFilterType,
-          _selectedOutputFilterType,
-        ),
-        returnValueForMissingStub: null,
-      );
+  _i4.OutputFilterTypeController get outputTypeController =>
+      (super.noSuchMethod(
+        Invocation.getter(#outputTypeController),
+        returnValue: _FakeOutputFilterTypeController_2(),
+      ) as _i4.OutputFilterTypeController);
   @override
-  String get outputResult => (super.noSuchMethod(
-        Invocation.getter(#outputResult),
-        returnValue: '',
-      ) as String);
+  _i5.CodeRunner get codeRunner => (super.noSuchMethod(
+        Invocation.getter(#codeRunner),
+        returnValue: _FakeCodeRunner_3(),
+      ) as _i5.CodeRunner);
   @override
-  set outputResult(String? _outputResult) => super.noSuchMethod(
+  set codeRunner(_i5.CodeRunner? _codeRunner) => super.noSuchMethod(
         Invocation.setter(
-          #outputResult,
-          _outputResult,
+          #codeRunner,
+          _codeRunner,
         ),
         returnValueForMissingStub: null,
       );
   @override
-  _i4.BeamShortcut get runShortcut => (super.noSuchMethod(
+  _i6.BeamShortcut get runShortcut => (super.noSuchMethod(
         Invocation.getter(#runShortcut),
-        returnValue: _FakeBeamShortcut_2(),
-      ) as _i4.BeamShortcut);
+        returnValue: _FakeBeamShortcut_4(),
+      ) as _i6.BeamShortcut);
   @override
-  set runShortcut(_i4.BeamShortcut? _runShortcut) => super.noSuchMethod(
+  set runShortcut(_i6.BeamShortcut? _runShortcut) => super.noSuchMethod(
         Invocation.setter(
           #runShortcut,
           _runShortcut,
@@ -119,12 +118,12 @@ class MockPlaygroundController extends _i1.Mock
         returnValueForMissingStub: null,
       );
   @override
-  _i4.BeamShortcut get resetShortcut => (super.noSuchMethod(
+  _i6.BeamShortcut get resetShortcut => (super.noSuchMethod(
         Invocation.getter(#resetShortcut),
-        returnValue: _FakeBeamShortcut_2(),
-      ) as _i4.BeamShortcut);
+        returnValue: _FakeBeamShortcut_4(),
+      ) as _i6.BeamShortcut);
   @override
-  set resetShortcut(_i4.BeamShortcut? _resetShortcut) => super.noSuchMethod(
+  set resetShortcut(_i6.BeamShortcut? _resetShortcut) => super.noSuchMethod(
         Invocation.setter(
           #resetShortcut,
           _resetShortcut,
@@ -137,41 +136,31 @@ class MockPlaygroundController extends _i1.Mock
         returnValue: '',
       ) as String);
   @override
-  bool get isCodeRunning => (super.noSuchMethod(
-        Invocation.getter(#isCodeRunning),
-        returnValue: false,
-      ) as bool);
-  @override
-  bool get isExampleChanged => (super.noSuchMethod(
-        Invocation.getter(#isExampleChanged),
-        returnValue: false,
-      ) as bool);
-  @override
   bool get graphAvailable => (super.noSuchMethod(
         Invocation.getter(#graphAvailable),
         returnValue: false,
       ) as bool);
   @override
-  List<_i4.BeamShortcut> get shortcuts => (super.noSuchMethod(
+  List<_i6.BeamShortcut> get shortcuts => (super.noSuchMethod(
         Invocation.getter(#shortcuts),
-        returnValue: <_i4.BeamShortcut>[],
-      ) as List<_i4.BeamShortcut>);
+        returnValue: <_i6.BeamShortcut>[],
+      ) as List<_i6.BeamShortcut>);
   @override
   bool get hasListeners => (super.noSuchMethod(
         Invocation.getter(#hasListeners),
         returnValue: false,
       ) as bool);
   @override
-  _i5.SnippetEditingController requireSnippetEditingController() =>
+  _i7.SnippetEditingController requireSnippetEditingController() =>
       (super.noSuchMethod(
         Invocation.method(
           #requireSnippetEditingController,
           [],
         ),
-        returnValue: _FakeSnippetEditingController_3(),
-      ) as _i5.SnippetEditingController);
+        returnValue: _FakeSnippetEditingController_5(),
+      ) as _i7.SnippetEditingController);
   @override
-  void setEmptyIfNoSdk(_i12.Sdk? sdk) => super.noSuchMethod(
+  void setEmptyIfNoSdk(_i13.Sdk? sdk) => super.noSuchMethod(
         Invocation.method(
           #setEmptyIfNoSdk,
           [sdk],
@@ -180,7 +169,7 @@ class MockPlaygroundController extends _i1.Mock
       );
   @override
   void setEmptyIfNotExists(
-    _i12.Sdk? sdk, {
+    _i13.Sdk? sdk, {
     bool? setCurrentSdk,
   }) =>
       super.noSuchMethod(
@@ -192,9 +181,19 @@ class MockPlaygroundController extends _i1.Mock
         returnValueForMissingStub: null,
       );
   @override
+  _i14.Future<void> setExampleBase(_i10.ExampleBase? exampleBase) =>
+      (super.noSuchMethod(
+        Invocation.method(
+          #setExampleBase,
+          [exampleBase],
+        ),
+        returnValue: Future<void>.value(),
+        returnValueForMissingStub: Future<void>.value(),
+      ) as _i14.Future<void>);
+  @override
   void setExample(
-    _i9.Example? example, {
-    _i13.ExampleLoadingDescriptor? descriptor,
+    _i11.Example? example, {
+    _i15.ExampleLoadingDescriptor? descriptor,
     bool? setCurrentSdk,
   }) =>
       super.noSuchMethod(
@@ -210,7 +209,7 @@ class MockPlaygroundController extends _i1.Mock
       );
   @override
   void setSdk(
-    _i12.Sdk? sdk, {
+    _i13.Sdk? sdk, {
     bool? notify = true,
   }) =>
       super.noSuchMethod(
@@ -222,41 +221,18 @@ class MockPlaygroundController extends _i1.Mock
         returnValueForMissingStub: null,
       );
   @override
-  void setSelectedOutputFilterType(_i11.OutputType? type) => 
super.noSuchMethod(
-        Invocation.method(
-          #setSelectedOutputFilterType,
-          [type],
-        ),
-        returnValueForMissingStub: null,
-      );
-  @override
-  void setOutputResult(String? outputs) => super.noSuchMethod(
-        Invocation.method(
-          #setOutputResult,
-          [outputs],
-        ),
-        returnValueForMissingStub: null,
-      );
-  @override
-  void clearOutput() => super.noSuchMethod(
-        Invocation.method(
-          #clearOutput,
-          [],
-        ),
-        returnValueForMissingStub: null,
-      );
-  @override
-  void reset() => super.noSuchMethod(
+  _i14.Future<void> reset() => (super.noSuchMethod(
         Invocation.method(
           #reset,
           [],
         ),
-        returnValueForMissingStub: null,
-      );
+        returnValue: Future<void>.value(),
+        returnValueForMissingStub: Future<void>.value(),
+      ) as _i14.Future<void>);
   @override
-  void resetError() => super.noSuchMethod(
+  void resetErrorMessageText() => super.noSuchMethod(
         Invocation.method(
-          #resetError,
+          #resetErrorMessageText,
           [],
         ),
         returnValueForMissingStub: null,
@@ -270,70 +246,44 @@ class MockPlaygroundController extends _i1.Mock
         returnValueForMissingStub: null,
       );
   @override
-  void runCode({void Function()? onFinish}) => super.noSuchMethod(
-        Invocation.method(
-          #runCode,
-          [],
-          {#onFinish: onFinish},
-        ),
-        returnValueForMissingStub: null,
-      );
-  @override
-  _i14.Future<void> cancelRun() => (super.noSuchMethod(
-        Invocation.method(
-          #cancelRun,
-          [],
-        ),
-        returnValue: Future<void>.value(),
-        returnValueForMissingStub: Future<void>.value(),
-      ) as _i14.Future<void>);
-  @override
-  void filterOutput(_i11.OutputType? type) => super.noSuchMethod(
-        Invocation.method(
-          #filterOutput,
-          [type],
-        ),
-        returnValueForMissingStub: null,
-      );
-  @override
-  _i14.Future<_i6.UserSharedExampleLoadingDescriptor> saveSnippet() =>
+  _i14.Future<_i8.UserSharedExampleLoadingDescriptor> saveSnippet() =>
       (super.noSuchMethod(
         Invocation.method(
           #saveSnippet,
           [],
         ),
-        returnValue: Future<_i6.UserSharedExampleLoadingDescriptor>.value(
-            _FakeUserSharedExampleLoadingDescriptor_4()),
-      ) as _i14.Future<_i6.UserSharedExampleLoadingDescriptor>);
+        returnValue: Future<_i8.UserSharedExampleLoadingDescriptor>.value(
+            _FakeUserSharedExampleLoadingDescriptor_6()),
+      ) as _i14.Future<_i8.UserSharedExampleLoadingDescriptor>);
   @override
-  _i7.ExamplesLoadingDescriptor getLoadingDescriptor() => (super.noSuchMethod(
+  _i9.ExamplesLoadingDescriptor getLoadingDescriptor() => (super.noSuchMethod(
         Invocation.method(
           #getLoadingDescriptor,
           [],
         ),
-        returnValue: _FakeExamplesLoadingDescriptor_5(),
-      ) as _i7.ExamplesLoadingDescriptor);
+        returnValue: _FakeExamplesLoadingDescriptor_7(),
+      ) as _i9.ExamplesLoadingDescriptor);
   @override
-  void addListener(_i15.VoidCallback? listener) => super.noSuchMethod(
+  void dispose() => super.noSuchMethod(
         Invocation.method(
-          #addListener,
-          [listener],
+          #dispose,
+          [],
         ),
         returnValueForMissingStub: null,
       );
   @override
-  void removeListener(_i15.VoidCallback? listener) => super.noSuchMethod(
+  void addListener(_i16.VoidCallback? listener) => super.noSuchMethod(
         Invocation.method(
-          #removeListener,
+          #addListener,
           [listener],
         ),
         returnValueForMissingStub: null,
       );
   @override
-  void dispose() => super.noSuchMethod(
+  void removeListener(_i16.VoidCallback? listener) => super.noSuchMethod(
         Invocation.method(
-          #dispose,
-          [],
+          #removeListener,
+          [listener],
         ),
         returnValueForMissingStub: null,
       );
@@ -356,16 +306,16 @@ class MockExampleCache extends _i1.Mock implements 
_i2.ExampleCache {
   }
 
   @override
-  Map<_i12.Sdk, List<_i16.CategoryWithExamples>> get categoryListsBySdk =>
+  Map<_i13.Sdk, List<_i17.CategoryWithExamples>> get categoryListsBySdk =>
       (super.noSuchMethod(
         Invocation.getter(#categoryListsBySdk),
-        returnValue: <_i12.Sdk, List<_i16.CategoryWithExamples>>{},
-      ) as Map<_i12.Sdk, List<_i16.CategoryWithExamples>>);
+        returnValue: <_i13.Sdk, List<_i17.CategoryWithExamples>>{},
+      ) as Map<_i13.Sdk, List<_i17.CategoryWithExamples>>);
   @override
-  Map<_i12.Sdk, _i9.Example> get defaultExamplesBySdk => (super.noSuchMethod(
+  Map<_i13.Sdk, _i11.Example> get defaultExamplesBySdk => (super.noSuchMethod(
         Invocation.getter(#defaultExamplesBySdk),
-        returnValue: <_i12.Sdk, _i9.Example>{},
-      ) as Map<_i12.Sdk, _i9.Example>);
+        returnValue: <_i13.Sdk, _i11.Example>{},
+      ) as Map<_i13.Sdk, _i11.Example>);
   @override
   bool get isSelectorOpened => (super.noSuchMethod(
         Invocation.getter(#isSelectorOpened),
@@ -385,10 +335,10 @@ class MockExampleCache extends _i1.Mock implements 
_i2.ExampleCache {
         returnValue: Future<void>.value(),
       ) as _i14.Future<void>);
   @override
-  _i17.LoadingStatus get catalogStatus => (super.noSuchMethod(
+  _i18.LoadingStatus get catalogStatus => (super.noSuchMethod(
         Invocation.getter(#catalogStatus),
-        returnValue: _i17.LoadingStatus.loading,
-      ) as _i17.LoadingStatus);
+        returnValue: _i18.LoadingStatus.loading,
+      ) as _i18.LoadingStatus);
   @override
   bool get hasListeners => (super.noSuchMethod(
         Invocation.getter(#hasListeners),
@@ -404,18 +354,18 @@ class MockExampleCache extends _i1.Mock implements 
_i2.ExampleCache {
         returnValueForMissingStub: Future<void>.value(),
       ) as _i14.Future<void>);
   @override
-  List<_i16.CategoryWithExamples> getCategories(_i12.Sdk? sdk) =>
+  List<_i17.CategoryWithExamples> getCategories(_i13.Sdk? sdk) =>
       (super.noSuchMethod(
         Invocation.method(
           #getCategories,
           [sdk],
         ),
-        returnValue: <_i16.CategoryWithExamples>[],
-      ) as List<_i16.CategoryWithExamples>);
+        returnValue: <_i17.CategoryWithExamples>[],
+      ) as List<_i17.CategoryWithExamples>);
   @override
-  _i14.Future<_i8.ExampleBase> getPrecompiledObject(
+  _i14.Future<_i10.ExampleBase> getPrecompiledObject(
     String? path,
-    _i12.Sdk? sdk,
+    _i13.Sdk? sdk,
   ) =>
       (super.noSuchMethod(
         Invocation.method(
@@ -425,20 +375,21 @@ class MockExampleCache extends _i1.Mock implements 
_i2.ExampleCache {
             sdk,
           ],
         ),
-        returnValue: Future<_i8.ExampleBase>.value(_FakeExampleBase_6()),
-      ) as _i14.Future<_i8.ExampleBase>);
+        returnValue: Future<_i10.ExampleBase>.value(_FakeExampleBase_8()),
+      ) as _i14.Future<_i10.ExampleBase>);
   @override
-  _i14.Future<_i9.Example> loadSharedExample(String? id) => 
(super.noSuchMethod(
+  _i14.Future<_i11.Example> loadSharedExample(String? id) =>
+      (super.noSuchMethod(
         Invocation.method(
           #loadSharedExample,
           [id],
         ),
-        returnValue: Future<_i9.Example>.value(_FakeExample_7()),
-      ) as _i14.Future<_i9.Example>);
+        returnValue: Future<_i11.Example>.value(_FakeExample_9()),
+      ) as _i14.Future<_i11.Example>);
   @override
   _i14.Future<String> saveSnippet({
-    List<_i18.SnippetFile>? files,
-    _i12.Sdk? sdk,
+    List<_i19.SnippetFile>? files,
+    _i13.Sdk? sdk,
     String? pipelineOptions,
   }) =>
       (super.noSuchMethod(
@@ -454,14 +405,14 @@ class MockExampleCache extends _i1.Mock implements 
_i2.ExampleCache {
         returnValue: Future<String>.value(''),
       ) as _i14.Future<String>);
   @override
-  _i14.Future<_i9.Example> loadExampleInfo(_i8.ExampleBase? example) =>
+  _i14.Future<_i11.Example> loadExampleInfo(_i10.ExampleBase? example) =>
       (super.noSuchMethod(
         Invocation.method(
           #loadExampleInfo,
           [example],
         ),
-        returnValue: Future<_i9.Example>.value(_FakeExample_7()),
-      ) as _i14.Future<_i9.Example>);
+        returnValue: Future<_i11.Example>.value(_FakeExample_9()),
+      ) as _i14.Future<_i11.Example>);
   @override
   void setSelectorOpened(bool? value) => super.noSuchMethod(
         Invocation.method(
@@ -471,14 +422,14 @@ class MockExampleCache extends _i1.Mock implements 
_i2.ExampleCache {
         returnValueForMissingStub: null,
       );
   @override
-  _i14.Future<_i9.Example?> getDefaultExampleBySdk(_i12.Sdk? sdk) =>
+  _i14.Future<_i11.Example?> getDefaultExampleBySdk(_i13.Sdk? sdk) =>
       (super.noSuchMethod(
         Invocation.method(
           #getDefaultExampleBySdk,
           [sdk],
         ),
-        returnValue: Future<_i9.Example?>.value(),
-      ) as _i14.Future<_i9.Example?>);
+        returnValue: Future<_i11.Example?>.value(),
+      ) as _i14.Future<_i11.Example?>);
   @override
   _i14.Future<void> loadDefaultPrecompiledObjects() => (super.noSuchMethod(
         Invocation.method(
@@ -498,16 +449,16 @@ class MockExampleCache extends _i1.Mock implements 
_i2.ExampleCache {
         returnValueForMissingStub: Future<void>.value(),
       ) as _i14.Future<void>);
   @override
-  _i14.Future<_i8.ExampleBase?> getCatalogExampleByPath(String? path) =>
+  _i14.Future<_i10.ExampleBase?> getCatalogExampleByPath(String? path) =>
       (super.noSuchMethod(
         Invocation.method(
           #getCatalogExampleByPath,
           [path],
         ),
-        returnValue: Future<_i8.ExampleBase?>.value(),
-      ) as _i14.Future<_i8.ExampleBase?>);
+        returnValue: Future<_i10.ExampleBase?>.value(),
+      ) as _i14.Future<_i10.ExampleBase?>);
   @override
-  void addListener(_i15.VoidCallback? listener) => super.noSuchMethod(
+  void addListener(_i16.VoidCallback? listener) => super.noSuchMethod(
         Invocation.method(
           #addListener,
           [listener],
@@ -515,7 +466,7 @@ class MockExampleCache extends _i1.Mock implements 
_i2.ExampleCache {
         returnValueForMissingStub: null,
       );
   @override
-  void removeListener(_i15.VoidCallback? listener) => super.noSuchMethod(
+  void removeListener(_i16.VoidCallback? listener) => super.noSuchMethod(
         Invocation.method(
           #removeListener,
           [listener],
diff --git 
a/playground/frontend/playground_components/test/src/controllers/playground_controller_test.dart
 
b/playground/frontend/playground_components/test/src/controllers/playground_controller_test.dart
index 30d39e293c9..628b1cc9443 100644
--- 
a/playground/frontend/playground_components/test/src/controllers/playground_controller_test.dart
+++ 
b/playground/frontend/playground_components/test/src/controllers/playground_controller_test.dart
@@ -54,13 +54,12 @@ Future<void> main() async {
     });
 
     test('Initial value of isCodeRunning should be false', () {
-      expect(controller.isCodeRunning, false);
+      expect(controller.codeRunner.isCodeRunning, false);
     });
 
     test('Initial value of pipelineOptions should be empty string', () {
-      expect(controller.pipelineOptions, null);
       controller.setSdk(Sdk.go);
-      expect(controller.pipelineOptions, '');
+      expect(controller.codeRunner.pipelineOptions, '');
     });
 
     test('source', () {
@@ -80,10 +79,10 @@ Future<void> main() async {
             descriptor: emptyDescriptor,
             setCurrentSdk: true,
           );
-          expect(controller.isExampleChanged, false);
+          expect(controller.codeRunner.isExampleChanged, false);
           controller.snippetEditingController?.fileControllers.first
               .codeController.text = 'test';
-          expect(controller.isExampleChanged, true);
+          expect(controller.codeRunner.isExampleChanged, true);
         },
       );
 
@@ -95,9 +94,9 @@ Future<void> main() async {
             descriptor: emptyDescriptor,
             setCurrentSdk: true,
           );
-          expect(controller.isExampleChanged, false);
+          expect(controller.codeRunner.isExampleChanged, false);
           controller.setPipelineOptions('test options');
-          expect(controller.isExampleChanged, true);
+          expect(controller.codeRunner.isExampleChanged, true);
         },
       );
     });
@@ -156,8 +155,8 @@ Future<void> main() async {
     test(
       'If Playground state result is empty, then resetError should break the 
execution',
       () {
-        controller.resetError();
-        expect(controller.result, null);
+        controller.resetErrorMessageText();
+        expect(controller.codeRunner.result, null);
       },
     );
 
@@ -166,7 +165,7 @@ Future<void> main() async {
       () {
         controller.setSdk(Sdk.go);
         controller.addListener(() {
-          expect(controller.pipelineOptions, 'test options');
+          expect(controller.codeRunner.pipelineOptions, 'test options');
         });
         controller.setPipelineOptions('test options');
       },


Reply via email to