This is an automated email from the ASF dual-hosted git repository. riemer pushed a commit to branch various-improvements-connect in repository https://gitbox.apache.org/repos/asf/streampipes.git
commit 91f764d57063f27bc6347597e671468cfc87a977 Author: Dominik Riemer <[email protected]> AuthorDate: Thu May 1 12:35:05 2025 +0200 fix: Improve adapter management --- .../management/management/GuessManagement.java | 2 +- .../management/management/WorkerRestClient.java | 4 +- .../management/connect/GuessManagement.java | 4 +- .../connectors/kafka/adapter/KafkaProtocol.java | 61 ++++++++++------------ .../standalone/manager/ProtocolManager.java | 15 +++--- .../event-schema/event-schema.component.html | 20 ++++--- .../event-schema/event-schema.component.ts | 40 ++++++++++++-- .../loading-message/loading-message.component.html | 8 +-- .../loading-message/loading-message.component.ts | 5 +- .../existing-adapters.component.html | 55 +++++++++++-------- .../existing-adapters.component.ts | 6 +++ .../delete-adapter-dialog.component.html | 49 ++++++++++------- .../delete-adapter-dialog.component.scss | 4 ++ ui/src/app/connect/services/rest.service.ts | 4 +- .../pipeline-status-dialog.component.html | 6 +-- ui/src/app/pipelines/pipelines.component.ts | 6 ++- 16 files changed, 181 insertions(+), 108 deletions(-) diff --git a/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/GuessManagement.java b/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/GuessManagement.java index b6dfbce037..c43be660bb 100644 --- a/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/GuessManagement.java +++ b/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/GuessManagement.java @@ -66,7 +66,7 @@ public class GuessManagement { ); var description = objectMapper.writeValueAsString(adapterDescription); - LOG.info("Guess schema at: " + workerUrl); + LOG.debug("Calling guess schema at: {}", workerUrl); Response requestResponse = ExtensionServiceExecutions .extServicePostRequest(workerUrl, description) .execute(); diff --git a/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/WorkerRestClient.java b/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/WorkerRestClient.java index 854c806a93..ca92a9c4fb 100644 --- a/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/WorkerRestClient.java +++ b/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/WorkerRestClient.java @@ -85,7 +85,7 @@ public class WorkerRestClient { private static void startAdapter(String url, AdapterDescription ad) throws AdapterException { - LOG.info("Trying to start adapter on endpoint {} ", url); + LOG.debug("Trying to start adapter on endpoint {} ", url); triggerAdapterStateChange(ad, url, "started"); } @@ -93,7 +93,7 @@ public class WorkerRestClient { private static void stopAdapter(AdapterDescription ad, String url) throws AdapterException { - LOG.info("Trying to stop adapter on endpoint {} ", url); + LOG.debug("Trying to stop adapter on endpoint {} ", url); triggerAdapterStateChange(ad, url, "stopped"); } diff --git a/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/connect/GuessManagement.java b/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/connect/GuessManagement.java index 6720c6a299..042a6e244a 100644 --- a/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/connect/GuessManagement.java +++ b/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/connect/GuessManagement.java @@ -53,14 +53,14 @@ public class GuessManagement { if (adapter.isPresent()) { var adapterInstance = adapter.get(); - LOG.info("Start guessing schema for: " + adapterDescription.getAppId()); + LOG.debug("Start guessing schema for: {}", adapterDescription.getAppId()); // get registered parser of adapter var registeredParsers = adapterInstance.declareConfig().getSupportedParsers(); var extractor = AdapterParameterExtractor.from(adapterDescription, registeredParsers); - LOG.info("Requesting the event schema for: " + adapterDescription.getAppId()); + LOG.info("Requesting the event schema for: {}", adapterDescription.getAppId()); try { var guessedSchemaObj = adapterInstance diff --git a/streampipes-extensions/streampipes-connectors-kafka/src/main/java/org/apache/streampipes/extensions/connectors/kafka/adapter/KafkaProtocol.java b/streampipes-extensions/streampipes-connectors-kafka/src/main/java/org/apache/streampipes/extensions/connectors/kafka/adapter/KafkaProtocol.java index ce10fcb759..ae8965cc08 100644 --- a/streampipes-extensions/streampipes-connectors-kafka/src/main/java/org/apache/streampipes/extensions/connectors/kafka/adapter/KafkaProtocol.java +++ b/streampipes-extensions/streampipes-connectors-kafka/src/main/java/org/apache/streampipes/extensions/connectors/kafka/adapter/KafkaProtocol.java @@ -52,11 +52,9 @@ import org.apache.streampipes.sdk.helpers.Locales; import org.apache.kafka.clients.consumer.Consumer; import org.apache.kafka.clients.consumer.ConsumerConfig; -import org.apache.kafka.clients.consumer.ConsumerRebalanceListener; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.consumer.KafkaConsumer; import org.apache.kafka.common.KafkaException; -import org.apache.kafka.common.TopicPartition; import org.apache.kafka.common.serialization.ByteArrayDeserializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -65,7 +63,6 @@ import java.io.ByteArrayInputStream; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Properties; @@ -217,45 +214,41 @@ public class KafkaProtocol implements StreamPipesAdapter, SupportsRuntimeConfig IAdapterGuessSchemaContext adapterGuessSchemaContext) throws AdapterException { this.applyConfiguration(extractor.getStaticPropertyExtractor()); - final Consumer<byte[], byte[]> consumer; - - consumer = createConsumer(this.config); - consumer.subscribe(Collections.singletonList(config.getTopic()), new ConsumerRebalanceListener() { - @Override - public void onPartitionsRevoked(Collection<TopicPartition> collection) { - - } - - @Override - public void onPartitionsAssigned(Collection<TopicPartition> collection) { - consumer.seekToBeginning(collection); - } - }); - List<byte[]> nEventsByte = new ArrayList<>(); List<byte[]> resultEventsByte; - int n = 1; + try (Consumer<byte[], byte[]> consumer = createConsumer(this.config)) { + LOG.debug("Created consumer for topic {}", config.getTopic()); - while (true) { - final ConsumerRecords<byte[], byte[]> consumerRecords = - consumer.poll(Duration.ofMillis(1000)); + consumer.subscribe(Collections.singletonList(config.getTopic())); - consumerRecords.forEach(record -> nEventsByte.add(record.value())); + long startTime = System.currentTimeMillis(); + long timeoutMillis = 15000; + int requiredEvents = 1; - if (nEventsByte.size() > n) { - resultEventsByte = nEventsByte.subList(0, n); - break; - } else if (nEventsByte.size() == n) { - resultEventsByte = nEventsByte; - break; - } + while (System.currentTimeMillis() - startTime < timeoutMillis) { + ConsumerRecords<byte[], byte[]> consumerRecords = consumer.poll(Duration.ofMillis(1000)); + LOG.debug("Polled {} records from topic {}", consumerRecords.count(), config.getTopic()); - consumer.commitAsync(); - } + consumerRecords.forEach(record -> { + if (record.value() != null) { + nEventsByte.add(record.value()); + } + }); + + if (nEventsByte.size() >= requiredEvents) { + resultEventsByte = nEventsByte.subList(0, requiredEvents); + LOG.info("Retrieved {} events from topic {}", resultEventsByte.size(), config.getTopic()); + return extractor.selectedParser().getGuessSchema(new ByteArrayInputStream(resultEventsByte.get(0))); + } + } - consumer.close(); + throw new AdapterException("Timeout reached (15 seconds). No events received from topic " + config.getTopic()); - return extractor.selectedParser().getGuessSchema(new ByteArrayInputStream(resultEventsByte.get(0))); + } catch (Exception e) { + throw new AdapterException("Error while guessing schema: " + e.getMessage(), e); + } finally { + LOG.debug("Closed consumer for topic {}", config.getTopic()); + } } } diff --git a/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/manager/ProtocolManager.java b/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/manager/ProtocolManager.java index 9b5a4f3e43..a6a448e894 100644 --- a/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/manager/ProtocolManager.java +++ b/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/manager/ProtocolManager.java @@ -61,8 +61,9 @@ public class ProtocolManager { return producers.get(topicName(protocol)); } else { producers.put(topicName(protocol), makeOutputCollector(protocol, resourceId)); - LOG.info("Adding new producer to producer map (size=" + producers.size() + "): " + topicName - (protocol)); + LOG.debug("Adding new producer to producer map (size={}): {}", + producers.size(), + topicName(protocol)); return producers.get(topicName(protocol)); } @@ -87,15 +88,17 @@ public class ProtocolManager { public static <T extends TransportProtocol> void removeInputCollector(T protocol) throws SpRuntimeException { consumers.remove(topicName(protocol)); - LOG.info("Removing consumer from consumer map (size=" + consumers.size() + "): " + topicName - (protocol)); + LOG.debug("Removing consumer from consumer map (size={}): {}", + consumers.size(), + topicName(protocol)); } public static <T extends TransportProtocol> void removeOutputCollector(T protocol) throws SpRuntimeException { producers.remove(topicName(protocol)); - LOG.info("Removing producer from producer map (size=" + producers.size() + "): " + topicName - (protocol)); + LOG.debug("Removing producer from producer map (size={}): {}", + producers.size(), + topicName(protocol)); } diff --git a/ui/src/app/connect/components/adapter-configuration/schema-editor/event-schema/event-schema.component.html b/ui/src/app/connect/components/adapter-configuration/schema-editor/event-schema/event-schema.component.html index 70fb70f697..38cdfcfcdf 100644 --- a/ui/src/app/connect/components/adapter-configuration/schema-editor/event-schema/event-schema.component.html +++ b/ui/src/app/connect/components/adapter-configuration/schema-editor/event-schema/event-schema.component.html @@ -18,13 +18,14 @@ <div fxLayout="column" fxLayoutAlign="center" class="mt-20"> <div fxFlex="90" fxLayout="column"> - <sp-event-schema-error-hints - [schemaErrorHints]="schemaErrorHints" - [isError]="isError" - [isLoading]="isLoading" - > - </sp-event-schema-error-hints> - + @if (!isError && !isLoading) { + <sp-event-schema-error-hints + [schemaErrorHints]="schemaErrorHints" + [isError]="isError" + [isLoading]="isLoading" + > + </sp-event-schema-error-hints> + } <sp-basic-inner-panel [showTitle]="false" outerMargin="0px 0px 20px 0px" @@ -60,7 +61,10 @@ </div> <div fxFlex="100"> - <sp-loading-message *ngIf="isLoading"></sp-loading-message> + <sp-loading-message + *ngIf="isLoading" + [currentProgress]="progress" + ></sp-loading-message> <sp-error-message [errorMessage]="errorMessage" diff --git a/ui/src/app/connect/components/adapter-configuration/schema-editor/event-schema/event-schema.component.ts b/ui/src/app/connect/components/adapter-configuration/schema-editor/event-schema/event-schema.component.ts index a74c30ab48..6414c29f73 100644 --- a/ui/src/app/connect/components/adapter-configuration/schema-editor/event-schema/event-schema.component.ts +++ b/ui/src/app/connect/components/adapter-configuration/schema-editor/event-schema/event-schema.component.ts @@ -21,6 +21,7 @@ import { EventEmitter, Input, OnChanges, + OnDestroy, Output, SimpleChanges, ViewChild, @@ -44,13 +45,14 @@ import { TransformationRuleService } from '../../../../services/transformation-r import { StaticValueTransformService } from '../../../../services/static-value-transform.service'; import { IdGeneratorService } from '../../../../../core-services/id-generator/id-generator.service'; import { SemanticType } from '@streampipes/platform-services'; +import { interval, Subscription } from 'rxjs'; @Component({ selector: 'sp-event-schema', templateUrl: './event-schema.component.html', styleUrls: ['./event-schema.component.scss'], }) -export class EventSchemaComponent implements OnChanges { +export class EventSchemaComponent implements OnChanges, OnDestroy { constructor( private restService: RestService, private transformationRuleService: TransformationRuleService, @@ -105,6 +107,9 @@ export class EventSchemaComponent implements OnChanges { desiredPreview: Record<string, any>; fieldStatusInfo: Record<string, FieldStatusInfo>; + progress = 0; + progressSub: Subscription; + options: ITreeOptions = { childrenField: 'eventProperties', allowDrag: () => { @@ -136,8 +141,26 @@ export class EventSchemaComponent implements OnChanges { public guessSchema(): void { this.isLoading = true; this.isError = false; + + this.progress = 0; + + const duration = 18000; + const tickRate = 150; + const totalTicks = duration / tickRate; + let tick = 0; + + this.progressSub = interval(tickRate).subscribe(() => { + tick++; + this.progress = (tick / totalTicks) * 100; + + if (tick >= totalTicks) { + this.stopProgress(); + } + }); + this.restService.getGuessSchema(this.adapterDescription).subscribe( guessSchema => { + this.progress = 100; this.eventPreview = guessSchema.eventPreview; this.fieldStatusInfo = guessSchema.fieldStatusInfo; this.targetSchema = guessSchema.targetSchema; @@ -151,7 +174,7 @@ export class EventSchemaComponent implements OnChanges { this.isEditable = true; this.isEditableChange.emit(true); - this.isLoading = false; + this.stopProgress(); this.refreshedEventSchema = true; this.refreshTree(); if ( @@ -164,12 +187,19 @@ export class EventSchemaComponent implements OnChanges { errorMessage => { this.errorMessage = errorMessage.error; this.isError = true; - this.isLoading = false; + this.stopProgress(); this.targetSchema = new EventSchema(); }, ); } + private stopProgress() { + this.progress = 100; + this.isLoading = false; + this.progressSub?.unsubscribe(); + this.progress = 0; + } + public refreshTree(refreshPreview = true): void { if (this.targetSchema && this.targetSchema.eventProperties) { this.nodes = new Array<EventPropertyUnion>(); @@ -343,4 +373,8 @@ export class EventSchemaComponent implements OnChanges { get tree(): TreeComponent { return this._tree; } + + ngOnDestroy() { + this.progressSub?.unsubscribe(); + } } diff --git a/ui/src/app/connect/components/adapter-configuration/schema-editor/loading-message/loading-message.component.html b/ui/src/app/connect/components/adapter-configuration/schema-editor/loading-message/loading-message.component.html index 04f9f71499..9f4f530265 100644 --- a/ui/src/app/connect/components/adapter-configuration/schema-editor/loading-message/loading-message.component.html +++ b/ui/src/app/connect/components/adapter-configuration/schema-editor/loading-message/loading-message.component.html @@ -18,15 +18,15 @@ <div fxLayout="column"> <div fxLayoutAlign="center"> - <mat-spinner + <mat-progress-bar fxLayoutAlign="center" style="margin: 10px 0 5px 0" color="accent" - [diameter]="30" - >Loading</mat-spinner + [value]="currentProgress" + >Loading</mat-progress-bar > </div> <div fxLayoutAlign="center"> - <h4>Guessing the event schema...</h4> + <h4>Looking at your data to understand the schema...</h4> </div> </div> diff --git a/ui/src/app/connect/components/adapter-configuration/schema-editor/loading-message/loading-message.component.ts b/ui/src/app/connect/components/adapter-configuration/schema-editor/loading-message/loading-message.component.ts index 4d3af332c3..86d7fa7a25 100644 --- a/ui/src/app/connect/components/adapter-configuration/schema-editor/loading-message/loading-message.component.ts +++ b/ui/src/app/connect/components/adapter-configuration/schema-editor/loading-message/loading-message.component.ts @@ -16,7 +16,7 @@ * */ -import { Component } from '@angular/core'; +import { Component, Input } from '@angular/core'; @Component({ selector: 'sp-loading-message', @@ -24,5 +24,8 @@ import { Component } from '@angular/core'; styleUrls: ['./loading-message.component.scss'], }) export class LoadingMessageComponent { + @Input() + currentProgress = 10; + constructor() {} } diff --git a/ui/src/app/connect/components/existing-adapters/existing-adapters.component.html b/ui/src/app/connect/components/existing-adapters/existing-adapters.component.html index 6147d2cf9c..24ed6f1379 100644 --- a/ui/src/app/connect/components/existing-adapters/existing-adapters.component.html +++ b/ui/src/app/connect/components/existing-adapters/existing-adapters.component.html @@ -123,28 +123,39 @@ *matCellDef="let adapter" data-cy="adapters-table" > - <button - color="accent" - mat-icon-button - matTooltip="Start adapter" - matTooltipPosition="above" - data-cy="start-adapter" - (click)="startAdapter(adapter)" - *ngIf="!adapter.running" - > - <i class="material-icons">play_arrow</i> - </button> - <button - color="accent" - mat-icon-button - matTooltip="Stop adapter" - matTooltipPosition="above" - data-cy="stop-adapter" - (click)="stopAdapter(adapter)" - *ngIf="adapter.running" - > - <i class="material-icons">stop</i> - </button> + @if ( + adapter.elementId === + operationInProgressAdapterId + ) { + <div fxLayoutAlign="center center"> + <mat-spinner + color="accent" + [diameter]="20" + ></mat-spinner> + </div> + } @else if (!adapter.running) { + <button + color="accent" + mat-icon-button + matTooltip="Start adapter" + matTooltipPosition="above" + data-cy="start-adapter" + (click)="startAdapter(adapter)" + > + <i class="material-icons">play_arrow</i> + </button> + } @else if (adapter.running) { + <button + color="accent" + mat-icon-button + matTooltip="Stop adapter" + matTooltipPosition="above" + data-cy="stop-adapter" + (click)="stopAdapter(adapter)" + > + <i class="material-icons">stop</i> + </button> + } </td> </ng-container> diff --git a/ui/src/app/connect/components/existing-adapters/existing-adapters.component.ts b/ui/src/app/connect/components/existing-adapters/existing-adapters.component.ts index bf409cbadd..7b3521d7bf 100644 --- a/ui/src/app/connect/components/existing-adapters/existing-adapters.component.ts +++ b/ui/src/app/connect/components/existing-adapters/existing-adapters.component.ts @@ -59,6 +59,7 @@ export class ExistingAdaptersComponent implements OnInit, OnDestroy { filteredAdapters: AdapterDescription[] = []; currentFilter: AdapterFilterSettingsModel; + operationInProgressAdapterId: string | undefined; @ViewChild(MatSort) sort: MatSort; @@ -117,22 +118,26 @@ export class ExistingAdaptersComponent implements OnInit, OnDestroy { } startAdapter(adapter: AdapterDescription) { + this.operationInProgressAdapterId = adapter.elementId; this.adapterService.startAdapter(adapter).subscribe( _ => { this.getAdaptersRunning(); }, error => { + this.operationInProgressAdapterId = undefined; this.openAdapterStatusErrorDialog(adapter, error.error, true); }, ); } stopAdapter(adapter: AdapterDescription, forceStop = false) { + this.operationInProgressAdapterId = adapter.elementId; this.adapterService.stopAdapter(adapter, forceStop).subscribe( _ => { this.getAdaptersRunning(); }, error => { + this.operationInProgressAdapterId = undefined; this.openAdapterStatusErrorDialog(adapter, error.error, false); }, ); @@ -265,6 +270,7 @@ export class ExistingAdaptersComponent implements OnInit, OnDestroy { this.existingAdapters = adapters; this.existingAdapters.sort((a, b) => a.name.localeCompare(b.name)); this.applyAdapterFilters(this.currentFilterIds); + this.operationInProgressAdapterId = undefined; this.getMonitoringInfos(adapters); setTimeout(() => { this.dataSource.sort = this.sort; diff --git a/ui/src/app/connect/dialog/delete-adapter-dialog/delete-adapter-dialog.component.html b/ui/src/app/connect/dialog/delete-adapter-dialog/delete-adapter-dialog.component.html index 0f08cec5c4..94a8d7d3a4 100644 --- a/ui/src/app/connect/dialog/delete-adapter-dialog/delete-adapter-dialog.component.html +++ b/ui/src/app/connect/dialog/delete-adapter-dialog/delete-adapter-dialog.component.html @@ -27,29 +27,40 @@ fxLayout="column" style="width: 100%" > - <div fxFlex="100" fxLayoutAlign="center center" fxLayout="column"> - <b> - <h4> - The adapter is currently used by these pipelines: - {{ namesOfPipelinesUsingAdapter }} - (if you don't see those pipelines, they are created by - other users) - </h4> - <h4> - You need to delete those pipelines first before deleting - the adapter. - </h4> - <h4> - Do you want to delete all associated pipelines and the - adapter? - </h4> - </b> + <div + fxFlex="100" + fxLayoutAlign="center center" + fxLayout="column" + class="info-text" + > + <p> + The adapter is currently used by these pipelines: + <b>{{ namesOfPipelinesUsingAdapter }}</b + ><br /> + (if you don't see those pipelines, they are created by other + users) + </p> + <p> + You need to delete those pipelines first before deleting the + adapter. + </p> + <p> + <b + >Do you want to delete all associated pipelines and the + adapter?</b + > + </p> </div> - <div fxFlex="100" fxLayoutAlign="center center" fxLayout="column"> + <div + fxFlex="100" + fxLayoutAlign="center center" + fxLayout="column" + class="mt-10" + > <button mat-button mat-raised-button - color="accent" + color="warn" (click)="deleteAdapter(true)" data-cy="delete-adapter-and-associated-pipelines-confirmation" > diff --git a/ui/src/app/connect/dialog/delete-adapter-dialog/delete-adapter-dialog.component.scss b/ui/src/app/connect/dialog/delete-adapter-dialog/delete-adapter-dialog.component.scss index fddade7bf6..d2b23dbfae 100644 --- a/ui/src/app/connect/dialog/delete-adapter-dialog/delete-adapter-dialog.component.scss +++ b/ui/src/app/connect/dialog/delete-adapter-dialog/delete-adapter-dialog.component.scss @@ -17,3 +17,7 @@ */ @import '../../../../scss/sp/sp-dialog.scss'; + +.info-text { + font-size: 12pt; +} diff --git a/ui/src/app/connect/services/rest.service.ts b/ui/src/app/connect/services/rest.service.ts index 8b7c46f426..04b9f6b0ce 100644 --- a/ui/src/app/connect/services/rest.service.ts +++ b/ui/src/app/connect/services/rest.service.ts @@ -45,7 +45,9 @@ export class RestService { getGuessSchema(adapter: AdapterDescription): Observable<GuessSchema> { return this.http - .post(`${this.connectPath}/master/guess/schema`, adapter) + .post(`${this.connectPath}/master/guess/schema`, adapter, { + context: new HttpContext().set(NGX_LOADING_BAR_IGNORED, true), + }) .pipe( map(response => { return GuessSchema.fromData(response as GuessSchema); diff --git a/ui/src/app/pipelines/dialog/pipeline-status/pipeline-status-dialog.component.html b/ui/src/app/pipelines/dialog/pipeline-status/pipeline-status-dialog.component.html index fc31410e41..c62621f1df 100644 --- a/ui/src/app/pipelines/dialog/pipeline-status/pipeline-status-dialog.component.html +++ b/ui/src/app/pipelines/dialog/pipeline-status/pipeline-status-dialog.component.html @@ -32,7 +32,7 @@ <div fxFlex="100" fxLayoutAlign="center center"> <mat-spinner color="accent" - [diameter]="50" + [diameter]="30" fxLayoutAlign="center" style="margin: 10px 0 5px 0" > @@ -48,7 +48,7 @@ fxLayoutAlign="center center" fxLayout="column" > - <h3> + <span class="success-message"> @if (action === 0) { {{ 'Please wait while the pipeline is starting' @@ -60,7 +60,7 @@ | translate }} } - </h3> + </span> </div> </div> diff --git a/ui/src/app/pipelines/pipelines.component.ts b/ui/src/app/pipelines/pipelines.component.ts index b59bea31c8..cef3622c18 100644 --- a/ui/src/app/pipelines/pipelines.component.ts +++ b/ui/src/app/pipelines/pipelines.component.ts @@ -104,7 +104,7 @@ export class PipelinesComponent implements OnInit, OnDestroy { getFunctions() { this.functionsService.getActiveFunctions().subscribe(functions => { - this.functions = functions.map(f => f.functionId); + this.functions = functions.map(f => f.functionId).sort(); this.functionsReady = true; }); } @@ -112,7 +112,9 @@ export class PipelinesComponent implements OnInit, OnDestroy { getPipelines() { this.pipelines = []; this.pipelineService.getPipelines().subscribe(pipelines => { - this.pipelines = pipelines; + this.pipelines = pipelines.sort((a, b) => + a.name.localeCompare(b.name), + ); this.applyPipelineFilters(this.currentFilters); }); }
