Skip to content

Commit

Permalink
feat(): Better response from bulk actions
Browse files Browse the repository at this point in the history
  • Loading branch information
Skraye committed Jan 18, 2023
1 parent 1cf61bf commit a12a337
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 53 deletions.
30 changes: 24 additions & 6 deletions ui/src/components/executions/Executions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -289,11 +289,17 @@
sort: this.$route.query.sort || "state.startDate:desc",
state: this.$route.query.state ? [this.$route.query.state] : this.statuses
}, false))
.then(_ => this.loadData())
.then(r => {
this.$toast().success(this.$t('executions restarted', {executionCount: r.data}));
this.loadData();
})
} else {
return this.$store
.dispatch("execution/bulkRestartExecution", {executionsId: this.executionsSelection})
.then(_ => this.loadData())
.then(r => {
this.$toast().success(this.$t('executions restarted', {executionCount: r.data}));
this.loadData();
})
}
}
)
Expand All @@ -308,11 +314,17 @@
sort: this.$route.query.sort || "state.startDate:desc",
state: this.$route.query.state ? [this.$route.query.state] : this.statuses
}, false))
.then(_ => this.loadData())
.then(r => {
this.$toast().success(this.$t('executions deleted', {executionCount: r.data}));
this.loadData();
})
} else {
return this.$store
.dispatch("execution/bulkDeleteExecution", {executionsId: this.executionsSelection})
.then(_ => this.loadData())
.then(r => {
this.$toast().success(this.$t('executions deleted', {executionCount: r.data}));
this.loadData();
})
}
}
)
Expand All @@ -327,11 +339,17 @@
sort: this.$route.query.sort || "state.startDate:desc",
state: this.$route.query.state ? [this.$route.query.state] : this.statuses
}, false))
.then(_ => this.loadData())
.then(r => {
this.$toast().success(this.$t('executions killed', {executionCount: r.data}));
this.loadData();
})
} else {
return this.$store
.dispatch("execution/bulkKill", {executionsId: this.executionsSelection})
.then(_ => this.loadData())
.then(r => {
this.$toast().success(this.$t('executions killed', {executionCount: r.data}));
this.loadData();
})
}
}
)
Expand Down
10 changes: 5 additions & 5 deletions ui/src/stores/executions.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ export default {
},
bulkRestartExecution(_, options) {
return this.$http.post(
`/api/v1/executions/restart/list`,
`/api/v1/executions/restart/by-ids`,
options.executionsId
)
},
queryRestartExecution(_, options) {
return this.$http.post(
`/api/v1/executions/restart/search`,
`/api/v1/executions/restart/query`,
{},
{params: options}
)
Expand Down Expand Up @@ -61,10 +61,10 @@ export default {
return this.$http.delete(`/api/v1/executions/${options.id}/kill`);
},
bulkKill(_, options) {
return this.$http.delete(`/api/v1/executions/kill/list`, {data: options.executionsId});
return this.$http.delete(`/api/v1/executions/kill/by-ids`, {data: options.executionsId});
},
queryKill(_, options) {
return this.$http.delete(`/api/v1/executions/kill/search`, {params: options});
return this.$http.delete(`/api/v1/executions/kill/query`, {params: options});
},
loadExecution({commit}, options) {
return this.$http.get(`/api/v1/executions/${options.id}`).then(response => {
Expand Down Expand Up @@ -96,7 +96,7 @@ export default {
return this.$http.delete(`/api/v1/executions/by-ids`, {data: options.executionsId})
},
queryDeleteExecution({commit}, options) {
return this.$http.delete(`/api/v1/executions/search`, {params: options})
return this.$http.delete(`/api/v1/executions/query`, {params: options})
},
followExecution(_, options) {
return new EventSource(`${this.$http.defaults.baseURL}api/v1/executions/${options.id}/follow`);
Expand Down
10 changes: 8 additions & 2 deletions ui/src/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,10 @@
"selected": "<strong>{count}</strong> selected",
"all": "select all ({count})"
},
"cancel": "Cancel"
"cancel": "Cancel",
"executions restarted": "<code>{executionCount}</code> executions(s) restarted.",
"executions killed": "<code>{executionCount}</code> executions(s) killed.",
"executions deleted": "<code>{executionCount}</code> executions(s) deleted."
},
"fr": {
"id": "Identifiant",
Expand Down Expand Up @@ -500,6 +503,9 @@
"selected": "<strong>{count}</strong> sélectionnés",
"all": "tous sélectionnés ({count})"
},
"cancel": "Cancel"
"cancel": "Annuler",
"executions restarted": "<code>{executionCount}</code> exécution(s) redémarrée(s).",
"executions killed": "<code>{executionCount}</code> exécution(s) killed.",
"executions deleted": "<code>{executionCount}</code> exécution(s) supprimée(s)."
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.kestra.core.exceptions.InternalException;
import io.kestra.core.models.tasks.Task;
import io.kestra.core.models.validations.ManualConstraintViolation;
import io.kestra.core.runners.RunContext;
import io.kestra.core.runners.RunContextFactory;
import io.micronaut.context.annotation.Value;
Expand Down Expand Up @@ -57,6 +58,7 @@
import jakarta.inject.Inject;
import jakarta.inject.Named;

import javax.validation.ConstraintViolationException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
Expand Down Expand Up @@ -258,38 +260,45 @@ public HttpResponse<Void> delete(
}
}

@Delete(uri = "executions/list", produces = MediaType.TEXT_JSON)
@Delete(uri = "executions/by-ids", produces = MediaType.TEXT_JSON)
@ExecuteOn(TaskExecutors.IO)
@Operation(tags = {"Executions"}, summary = "Delete a list of executions")
@ApiResponses(
@ApiResponse(responseCode = "204", description = "On success")
)
public MutableHttpResponse bulkDelete(
public HttpResponse<Integer> bulkDelete(
@Parameter(description = "The execution id") @Body List<String> executionsId
) {
List<Execution> executions = new ArrayList<>();
List<String> executionsNotFound = new ArrayList<>();
Set<ManualConstraintViolation<String>> invalids = new HashSet<>();

for (String executionId : executionsId) {
Optional<Execution> execution = executionRepository.findById(executionId);
if (execution.isPresent()) {
executions.add(execution.get());
} else {
executionsNotFound.add(executionId);
invalids.add(ManualConstraintViolation.of(
String.format("Execution %s not found", executionId),
executionId,
String.class,
"execution",
executionId
));
}
}
if (executionsNotFound.size() > 0) {
return HttpResponse.status(HttpStatus.NOT_FOUND, String.format("One or more executions were not found : %s", executionsNotFound));
if (invalids.size() > 0) {
throw new ConstraintViolationException("invalid execution",invalids);
}
for (Execution execution : executions) {
executionRepository.delete(execution);
}
return HttpResponse.status(HttpStatus.NO_CONTENT);
return HttpResponse.ok(executions.size());
}

@Delete(uri = "executions/query", produces = MediaType.TEXT_JSON)
@ExecuteOn(TaskExecutors.IO)
@Operation(tags = {"Executions"}, summary = "Delete executions returned by the query")
public MutableHttpResponse queryDelete(
public HttpResponse<Integer> queryDelete(
@Parameter(description = "A string filter") @Nullable @QueryValue(value = "q") String query,
@Parameter(description = "A namespace filter prefix") @Nullable String namespace,
@Parameter(description = "A flow id filter") @Nullable String flowId,
Expand All @@ -305,13 +314,13 @@ public MutableHttpResponse queryDelete(
endDate,
state
);
Integer result = executions.map(e -> {
Integer count = executions.map(e -> {
executionRepository.delete(e);
return 1;
}
).reduce(Integer::sum).blockingGet();

return HttpResponse.status(HttpStatus.NO_CONTENT, String.format("Deleted %s executions", result));
return HttpResponse.ok(count);
}


Expand Down Expand Up @@ -560,44 +569,53 @@ public Execution restart(
}

@ExecuteOn(TaskExecutors.IO)
@Post(uri = "executions/restart/list", produces = MediaType.TEXT_JSON)
@Post(uri = "executions/restart/by-ids", produces = MediaType.TEXT_JSON)
@Operation(tags = {"Executions"}, summary = "Restart a list of executions")
public MutableHttpResponse<Object> bulkRestart(
public HttpResponse<Integer> bulkRestart(
@Parameter(description = "The execution id") @Body List<String> executionsId
) throws Exception {
List<Execution> executions = new ArrayList<>();
List<String> executionsNotFound = new ArrayList<>();
List<String> executionsNotFailed = new ArrayList<>();
Set<ManualConstraintViolation<String>> invalids = new HashSet<>();

for (String executionId : executionsId) {
Optional<Execution> execution = executionRepository.findById(executionId);
if (execution.isPresent() && !execution.get().getState().isFailed()) {
executionsNotFailed.add(executionId);
invalids.add(ManualConstraintViolation.of(
String.format("Execution %s is not in state FAILED", executionId),
executionId,
String.class,
"execution",
executionId
));
} else if (execution.isEmpty()) {
executionsNotFound.add(executionId);
invalids.add(ManualConstraintViolation.of(
String.format("Execution %s not found", executionId),
executionId,
String.class,
"execution",
executionId
));
} else {
executions.add(execution.get());

}
}
if (executionsNotFound.size() + executionsNotFailed.size() > 0) {

throw new IllegalStateException(String.format("One or more executions are not in state %s or were not found, can't restart them" +
"\nNot found: %s" +
"\nBad state: %s", State.Type.FAILED, executionsNotFound, executionsNotFailed));
if (invalids.size() > 0) {
throw new ConstraintViolationException("invalid execution",invalids);
}
for (Execution execution : executions) {
Execution restart = executionService.restart(execution, null);
executionQueue.emit(restart);
eventPublisher.publishEvent(new CrudEvent<>(restart, CrudEventType.UPDATE));
}

return HttpResponse.status(HttpStatus.NO_CONTENT);
return HttpResponse.ok(executions.size());
}

@ExecuteOn(TaskExecutors.IO)
@Post(uri = "executions/restart/query", produces = MediaType.TEXT_JSON)
@Operation(tags = {"Executions"}, summary = "Restart executions returned by the query")
public MutableHttpResponse<Object> queryRestart(
public HttpResponse<Integer> queryRestart(
@Parameter(description = "A string filter") @Nullable @QueryValue(value = "q") String query,
@Parameter(description = "A namespace filter prefix") @Nullable String namespace,
@Parameter(description = "A flow id filter") @Nullable String flowId,
Expand All @@ -613,15 +631,15 @@ public MutableHttpResponse<Object> queryRestart(
endDate,
state
);
Integer result = executions.map(e -> {
Integer count = executions.map(e -> {
Execution restart = executionService.restart(e, null);
executionQueue.emit(restart);
eventPublisher.publishEvent(new CrudEvent<>(restart, CrudEventType.UPDATE));
return 1;
}
).reduce(Integer::sum).blockingGet();

return HttpResponse.status(HttpStatus.NO_CONTENT, String.format("Restarted %s executions", result));
return HttpResponse.ok(count);
}

@ExecuteOn(TaskExecutors.IO)
Expand Down Expand Up @@ -715,35 +733,44 @@ public HttpResponse<?> kill(
}

@ExecuteOn(TaskExecutors.IO)
@Delete(uri = "executions/kill/list", produces = MediaType.TEXT_JSON)
@Delete(uri = "executions/kill/by-ids", produces = MediaType.TEXT_JSON)
@Operation(tags = {"Executions"}, summary = "Kill a list of executions")
@ApiResponses(
value = {
@ApiResponse(responseCode = "204", description = "On success"),
@ApiResponse(responseCode = "409", description = "if the executions is already finished")
}
)
public HttpResponse<?> bulkKill(
public HttpResponse<Integer> bulkKill(
@Parameter(description = "The execution id") @Body List<String> executionsId
) {
List<Execution> executions = new ArrayList<>();
List<String> executionsNotFound = new ArrayList<>();
List<String> executionsFinished = new ArrayList<>();
Set<ManualConstraintViolation<String>> invalids = new HashSet<>();

for (String executionId : executionsId) {
Optional<Execution> execution = executionRepository.findById(executionId);
if (execution.isPresent() && execution.get().getState().isTerninated()) {
executionsFinished.add(executionId);
invalids.add(ManualConstraintViolation.of(
String.format("Execution %s is already finished", executionId),
executionId,
String.class,
"execution",
executionId
));
} else if (execution.isEmpty()) {
executionsNotFound.add(executionId);
invalids.add(ManualConstraintViolation.of(
String.format("Execution % not found", executionId),
executionId,
String.class,
"execution",
executionId
));
} else {
executions.add(execution.get());

}
}
if (executionsNotFound.size() > 0) {
throw new IllegalStateException(String.format("One or more executions are already finished or were not found, can't kill them" +
"\nNot found: %s" +
"\nAlready finished: %s", executionsNotFound, executionsFinished));
if (invalids.size() > 0) {
throw new ConstraintViolationException("invalid execution",invalids);
}
for (Execution execution : executions) {
killQueue.emit(ExecutionKilled
Expand All @@ -752,13 +779,13 @@ public HttpResponse<?> bulkKill(
.build()
);
}
return HttpResponse.status(HttpStatus.NO_CONTENT);
return HttpResponse.ok(executions.size());
}

@ExecuteOn(TaskExecutors.IO)
@Delete(uri = "executions/kill/query", produces = MediaType.TEXT_JSON)
@Operation(tags = {"Executions"}, summary = "Kill executions returned by the query")
public HttpResponse<?> queryKill(
public HttpResponse<Integer> queryKill(
@Parameter(description = "A string filter") @Nullable @QueryValue(value = "q") String query,
@Parameter(description = "A namespace filter prefix") @Nullable String namespace,
@Parameter(description = "A flow id filter") @Nullable String flowId,
Expand All @@ -774,7 +801,7 @@ public HttpResponse<?> queryKill(
endDate,
state
);
Integer result = executions.map(e -> {
Integer count = executions.map(e -> {
killQueue.emit(ExecutionKilled
.builder()
.executionId(e.getId())
Expand All @@ -783,7 +810,7 @@ public HttpResponse<?> queryKill(
}
).reduce(Integer::sum).blockingGet();

return HttpResponse.status(HttpStatus.NO_CONTENT, String.format("Killed %s executions", result));
return HttpResponse.ok(count);
}

private boolean isStopFollow(Flow flow, Execution execution) {
Expand Down

0 comments on commit a12a337

Please sign in to comment.