From 070fc2a101fc07614cea8d4eeef9821a0d3ccbbb Mon Sep 17 00:00:00 2001 From: John OHara Date: Wed, 11 Nov 2020 09:55:47 +0000 Subject: [PATCH 01/13] Fix docs and podman config * fix podman-compose cmd in readme. * Bind keycloak private to localhost in podman, otherwise WF fails to bootstrap * warning re cached postgres data * warning re cached npm modules --- README.md | 5 ++++- podman-compose.yml | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 889dfdb56..06c641246 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,11 @@ Note that this docker-compose script is intended for developer use; for producti If you are using Podman (and podman-compose) rather than Docker, please use ```bash -podman-compose -f podman-compose.yaml -d +podman-compose -f podman-compose.yml up -d ``` +> :warning: **If postgres fails to start**: clear any cached data in the postgresl container mounted volume `podman volume inspect Horreum_horreum_pg12 | jq -r '.[0].Mountpoint'` + Due to subtleties in Podman's rootless network configuration it's not possible to use `docker-compose.yaml`. ## Getting Started @@ -29,6 +31,7 @@ cd webapp && npm install && cd .. `localhost:3000` to access the create-react-app live code server and `localhost:8080` to access the quarkus development server. +> :warning: *If npm install fails*: please try clearing the node module cache `npm cache clean` ## Creating jar ```bash ./mvnw clean package -Dui diff --git a/podman-compose.yml b/podman-compose.yml index 55cc8c241..7aaa4faba 100644 --- a/podman-compose.yml +++ b/podman-compose.yml @@ -51,6 +51,7 @@ services: - -Dkeycloak.migration.strategy=IGNORE_EXISTING - -Djboss.socket.binding.port-offset=100 - -Djboss.bind.address=127.0.0.1 + - -Djboss.bind.address.private=127.0.0.1 environment: - KEYCLOAK_USER=admin - KEYCLOAK_PASSWORD=secret From 199e73d7619feb6cb284ad2c9f260d2ab8c04335 Mon Sep 17 00:00:00 2001 From: John OHara Date: Thu, 12 Nov 2020 12:42:57 +0000 Subject: [PATCH 02/13] Auto detect dev mode and enable Redux Devtools if in dev mode --- webapp/src/store.ts | 2 ++ webapp/src/utils.ts | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/webapp/src/store.ts b/webapp/src/store.ts index f4ea59b50..9c8551338 100644 --- a/webapp/src/store.ts +++ b/webapp/src/store.ts @@ -10,6 +10,7 @@ import { HooksState, reducer as hookReducer} from './domain/hooks/reducers' import { SchemasState, reducer as schemaReducer} from './domain/schemas/reducers' import { AuthState, reducer as authReducer} from './auth' import { Alert, reducer as alertReducer } from './alerts' +import {enableDevMode} from "./utils"; export const history = createBrowserHistory(); @@ -35,6 +36,7 @@ const enhancer = compose( applyMiddleware( thunk, ), + enableDevMode(), ) const store = createStore( appReducers, diff --git a/webapp/src/utils.ts b/webapp/src/utils.ts index 5f3f80754..adcbd810a 100644 --- a/webapp/src/utils.ts +++ b/webapp/src/utils.ts @@ -46,4 +46,12 @@ export type PaginationInfo = { perPage: number, sort: string, direction: string, +} + +export function enableDevMode() { + if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') { + return (window as any).__REDUX_DEVTOOLS_EXTENSION__ && (window as any).__REDUX_DEVTOOLS_EXTENSION__(); + } else { + return; + } } \ No newline at end of file From 6728ca2b171dafae8450333d99f5ab5ffc03c8c2 Mon Sep 17 00:00:00 2001 From: John OHara Date: Thu, 12 Nov 2020 14:11:00 +0000 Subject: [PATCH 03/13] Include OpenAPI dependency --- pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pom.xml b/pom.xml index f67addbc5..c7e56c9b2 100644 --- a/pom.xml +++ b/pom.xml @@ -194,6 +194,10 @@ io.quarkus quarkus-container-image-jib + + io.quarkus + quarkus-smallrye-openapi + io.vertx vertx-core From 76cea8e91737fba7405a9cfac3d90a4c4e99e3e2 Mon Sep 17 00:00:00 2001 From: John OHara Date: Thu, 12 Nov 2020 15:35:05 +0000 Subject: [PATCH 04/13] Extracted security properties of protected entities into base entity --- .../entity/json/ProtectedBaseEntity.java | 26 +++++++++++++++++++ .../tools/horreum/entity/json/Run.java | 21 +++++---------- .../tools/horreum/entity/json/Schema.java | 15 +---------- .../tools/horreum/entity/json/Test.java | 25 +++++++----------- 4 files changed, 43 insertions(+), 44 deletions(-) create mode 100644 src/main/java/io/hyperfoil/tools/horreum/entity/json/ProtectedBaseEntity.java diff --git a/src/main/java/io/hyperfoil/tools/horreum/entity/json/ProtectedBaseEntity.java b/src/main/java/io/hyperfoil/tools/horreum/entity/json/ProtectedBaseEntity.java new file mode 100644 index 000000000..e063e5b0d --- /dev/null +++ b/src/main/java/io/hyperfoil/tools/horreum/entity/json/ProtectedBaseEntity.java @@ -0,0 +1,26 @@ +package io.hyperfoil.tools.horreum.entity.json; + +import io.hyperfoil.tools.horreum.entity.converter.AccessSerializer; +import io.quarkus.hibernate.orm.panache.PanacheEntityBase; +import io.quarkus.runtime.annotations.RegisterForReflection; + +import javax.json.bind.annotation.JsonbTypeDeserializer; +import javax.json.bind.annotation.JsonbTypeSerializer; +import javax.persistence.MappedSuperclass; +import javax.validation.constraints.NotNull; + +@RegisterForReflection +@MappedSuperclass +public abstract class ProtectedBaseEntity extends PanacheEntityBase { + + @NotNull + public String owner; + + public String token; + + @NotNull + @JsonbTypeSerializer(AccessSerializer.class) + @JsonbTypeDeserializer(AccessSerializer.class) + public Access access = Access.PUBLIC; + +} diff --git a/src/main/java/io/hyperfoil/tools/horreum/entity/json/Run.java b/src/main/java/io/hyperfoil/tools/horreum/entity/json/Run.java index 1160f3181..e69673c02 100644 --- a/src/main/java/io/hyperfoil/tools/horreum/entity/json/Run.java +++ b/src/main/java/io/hyperfoil/tools/horreum/entity/json/Run.java @@ -1,21 +1,24 @@ package io.hyperfoil.tools.horreum.entity.json; -import io.hyperfoil.tools.horreum.entity.converter.AccessSerializer; import io.hyperfoil.tools.horreum.entity.converter.InstantSerializer; import io.hyperfoil.tools.yaup.json.Json; -import io.quarkus.hibernate.orm.panache.PanacheEntityBase; import io.quarkus.runtime.annotations.RegisterForReflection; import org.hibernate.annotations.Type; import javax.json.bind.annotation.JsonbTypeDeserializer; import javax.json.bind.annotation.JsonbTypeSerializer; -import javax.persistence.*; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.SequenceGenerator; import javax.validation.constraints.NotNull; import java.time.Instant; @Entity(name = "run") @RegisterForReflection -public class Run extends PanacheEntityBase { +public class Run extends ProtectedBaseEntity { public static final String EVENT_NEW = "run/new"; public static final String EVENT_TRASHED = "run/trashed"; @@ -49,16 +52,6 @@ public class Run extends PanacheEntityBase { @Type(type = "io.hyperfoil.tools.horreum.entity.converter.JsonUserType") public Json data; - @NotNull - public String owner; - - public String token; - - @NotNull - @JsonbTypeSerializer(AccessSerializer.class) - @JsonbTypeDeserializer(AccessSerializer.class) - public Access access = Access.PUBLIC; - @NotNull @Column(columnDefinition = "boolean default false") public boolean trashed; diff --git a/src/main/java/io/hyperfoil/tools/horreum/entity/json/Schema.java b/src/main/java/io/hyperfoil/tools/horreum/entity/json/Schema.java index 50a59fd1a..b2b2ac8ad 100644 --- a/src/main/java/io/hyperfoil/tools/horreum/entity/json/Schema.java +++ b/src/main/java/io/hyperfoil/tools/horreum/entity/json/Schema.java @@ -1,13 +1,9 @@ package io.hyperfoil.tools.horreum.entity.json; -import io.hyperfoil.tools.horreum.entity.converter.AccessSerializer; import io.hyperfoil.tools.yaup.json.Json; -import io.quarkus.hibernate.orm.panache.PanacheEntityBase; import io.quarkus.runtime.annotations.RegisterForReflection; import org.hibernate.annotations.Type; -import javax.json.bind.annotation.JsonbTypeDeserializer; -import javax.json.bind.annotation.JsonbTypeSerializer; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; @@ -24,7 +20,7 @@ name = "schema", uniqueConstraints = @UniqueConstraint(columnNames = {"owner", "uri"}) ) -public class Schema extends PanacheEntityBase { +public class Schema extends ProtectedBaseEntity { @Id @SequenceGenerator( @@ -66,13 +62,4 @@ public class Schema extends PanacheEntityBase { */ public String descriptionPath; - @NotNull - public String owner; - - public String token; - - @NotNull - @JsonbTypeSerializer(AccessSerializer.class) - @JsonbTypeDeserializer(AccessSerializer.class) - public Access access; } diff --git a/src/main/java/io/hyperfoil/tools/horreum/entity/json/Test.java b/src/main/java/io/hyperfoil/tools/horreum/entity/json/Test.java index 48df09a41..3aa5e3cf0 100644 --- a/src/main/java/io/hyperfoil/tools/horreum/entity/json/Test.java +++ b/src/main/java/io/hyperfoil/tools/horreum/entity/json/Test.java @@ -1,17 +1,20 @@ package io.hyperfoil.tools.horreum.entity.json; -import io.hyperfoil.tools.horreum.entity.converter.AccessSerializer; -import io.quarkus.hibernate.orm.panache.PanacheEntityBase; import io.quarkus.runtime.annotations.RegisterForReflection; -import javax.json.bind.annotation.JsonbTypeDeserializer; -import javax.json.bind.annotation.JsonbTypeSerializer; -import javax.persistence.*; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.SequenceGenerator; import javax.validation.constraints.NotNull; @Entity(name="test") @RegisterForReflection -public class Test extends PanacheEntityBase { +public class Test extends ProtectedBaseEntity { public static final String EVENT_NEW = "test/new"; @Id @@ -36,16 +39,6 @@ public class Test extends PanacheEntityBase { @OneToOne(cascade = { CascadeType.REMOVE, CascadeType.MERGE }) public View defaultView; - @NotNull - public String owner; - - public String token; - - @NotNull - @JsonbTypeSerializer(AccessSerializer.class) - @JsonbTypeDeserializer(AccessSerializer.class) - public Access access = Access.PUBLIC; - public String compareUrl; public void ensureLinked() { From 45d625d49be46b04246fc0f89531a1bf65271710 Mon Sep 17 00:00:00 2001 From: John OHara Date: Fri, 13 Nov 2020 12:32:08 +0000 Subject: [PATCH 05/13] Allow users to define webhooks for change/new event (i.e. regression events) --- .../io/hyperfoil/tools/horreum/api/HookService.java | 11 +++++++++++ webapp/src/domain/hooks/AddHookModal.tsx | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/hyperfoil/tools/horreum/api/HookService.java b/src/main/java/io/hyperfoil/tools/horreum/api/HookService.java index fa3e43864..57308896b 100644 --- a/src/main/java/io/hyperfoil/tools/horreum/api/HookService.java +++ b/src/main/java/io/hyperfoil/tools/horreum/api/HookService.java @@ -1,6 +1,7 @@ package io.hyperfoil.tools.horreum.api; import io.hyperfoil.tools.horreum.JsonAdapter; +import io.hyperfoil.tools.horreum.entity.alerting.Change; import io.hyperfoil.tools.horreum.entity.json.Hook; import io.hyperfoil.tools.horreum.entity.json.Run; import io.hyperfoil.tools.horreum.entity.json.Test; @@ -141,6 +142,16 @@ public void newRun(Run run) { tellHooks(Run.EVENT_NEW, testId, run); } + @Transactional + @ConsumeEvent(value = Change.EVENT_NEW, blocking = true) + public void newChange(Change.Event changeEvent) { + Integer runId = changeEvent.change.runId; + try (CloseMe h = sqlService.withRoles(em, identity)) { + Run run = Run.find("id", runId).firstResult(); + tellHooks(Change.EVENT_NEW, run.testid, changeEvent.change); + } + } + @RolesAllowed(Roles.ADMIN) @POST @Transactional diff --git a/webapp/src/domain/hooks/AddHookModal.tsx b/webapp/src/domain/hooks/AddHookModal.tsx index e8441fe7c..897ee0580 100644 --- a/webapp/src/domain/hooks/AddHookModal.tsx +++ b/webapp/src/domain/hooks/AddHookModal.tsx @@ -12,7 +12,7 @@ import { import { Hook } from './reducers'; import TestSelect, { SelectedTest } from '../../components/TestSelect' -const eventTypes = ["test/new","run/new"] +export const eventTypes = ["test/new","run/new","change/new"] const isValidUrl = (string: string) => { try { @@ -97,7 +97,7 @@ export default ({isOpen=false,onCancel=()=>{}, onSubmit=(validation: Hook)=>{}}) { - eventType === "run/new" && + ( eventType === "run/new" || eventType === "change/new" ) && Date: Fri, 13 Nov 2020 12:33:07 +0000 Subject: [PATCH 06/13] Define configuration for OpenAPI --- src/main/resources/application.properties | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 6f76cd45f..cb2a23995 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -34,7 +34,7 @@ quarkus.hibernate-orm.dialect=io.hyperfoil.tools.horreum.entity.converter.JsonPo #quarkus.hibernate-orm.database.generation=drop-and-create #quarkus.hibernate-orm.database.generation=update # import.sql is executed only in 'create' or 'drop-and-create' modes. -%insecure.quarkus.hibernate-orm.database.generation=drop-and-create +#%insecure.quarkus.hibernate-orm.database.generation=drop-and-create # By default (in production) the database is created using structure.sql - the default application user # does not have privileges to drop or alter the tables. quarkus.hibernate-orm.database.generation=validate @@ -74,4 +74,19 @@ quarkus.container-image.tag=latest quarkus.jib.base-jvm-image=quay.io/hyperfoil/horreum-base:latest quarkus.jib.jvm-entrypoint=/deployments/horreum.sh -quarkus.live-reload.password=secret \ No newline at end of file +quarkus.live-reload.password=secret + + +# openAPI definitions +mp.openapi.extensions.smallrye.info.title=Horreum API +%dev.mp.openapi.extensions.smallrye.info.title=Horreum API (development) +%test.mp.openapi.extensions.smallrye.info.title=Horreum API (test) +mp.openapi.extensions.smallrye.info.version=0.1-SNAPSHOT +mp.openapi.extensions.smallrye.info.description=Horreum data repository API +mp.openapi.extensions.smallrye.info.termsOfService=Here be dragons! +mp.openapi.extensions.smallrye.info.contact.email=techsupport@example.com +mp.openapi.extensions.smallrye.info.contact.name=Example API Support +mp.openapi.extensions.smallrye.info.contact.url=http://exampleurl.com/contact +mp.openapi.extensions.smallrye.info.license.name=Apache 2.0 +mp.openapi.extensions.smallrye.info.license.url=http://www.apache.org/licenses/LICENSE-2.0.html + From 9450d4d7a7a5006d8f0663a76193ab8a403c2b6a Mon Sep 17 00:00:00 2001 From: John OHara Date: Fri, 13 Nov 2020 12:34:21 +0000 Subject: [PATCH 07/13] Modify Hook entity to link to particular tests. Add test/{id}/hook path to link a new webhook to a particular test --- .../tools/horreum/api/TestService.java | 30 ++++++++++++++++--- .../tools/horreum/entity/json/Hook.java | 14 ++++----- src/main/resources/db/changeLog.xml | 8 +++++ 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/src/main/java/io/hyperfoil/tools/horreum/api/TestService.java b/src/main/java/io/hyperfoil/tools/horreum/api/TestService.java index 175dc717c..255b701a2 100644 --- a/src/main/java/io/hyperfoil/tools/horreum/api/TestService.java +++ b/src/main/java/io/hyperfoil/tools/horreum/api/TestService.java @@ -2,10 +2,7 @@ import io.agroal.api.AgroalDataSource; import io.hyperfoil.tools.horreum.entity.converter.JsonResultTransformer; -import io.hyperfoil.tools.horreum.entity.json.Access; -import io.hyperfoil.tools.horreum.entity.json.Test; -import io.hyperfoil.tools.horreum.entity.json.View; -import io.hyperfoil.tools.horreum.entity.json.ViewComponent; +import io.hyperfoil.tools.horreum.entity.json.*; import io.quarkus.panache.common.Page; import io.quarkus.panache.common.Sort; import io.quarkus.security.identity.SecurityIdentity; @@ -260,4 +257,29 @@ public Response updateView(@PathParam("testId") Integer testId, View view) { } return Response.noContent().build(); } + + @RolesAllowed("tester") + @POST + @Path("{testId}/hook") + public Response updateHook(@PathParam("testId") Integer testId, Hook hook) { + if (testId == null || testId <= 0) { + return Response.status(Response.Status.BAD_REQUEST).entity("Missing test id").build(); + } + try (@SuppressWarnings("unused") CloseMe closeMe = sqlService.withRoles(em, identity)) { + Test test = Test.findById(testId); + if (test == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } +// hook.ensureLinked(); + hook.test = test; + + if (hook.id == null) { + em.persist(hook); + } else { + em.merge(hook); + } + test.persist(); + } + return Response.noContent().build(); + } } diff --git a/src/main/java/io/hyperfoil/tools/horreum/entity/json/Hook.java b/src/main/java/io/hyperfoil/tools/horreum/entity/json/Hook.java index 2e745573e..5a0b32b6a 100644 --- a/src/main/java/io/hyperfoil/tools/horreum/entity/json/Hook.java +++ b/src/main/java/io/hyperfoil/tools/horreum/entity/json/Hook.java @@ -3,14 +3,8 @@ import io.quarkus.hibernate.orm.panache.PanacheEntityBase; import io.quarkus.runtime.annotations.RegisterForReflection; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.SequenceGenerator; -import javax.persistence.Table; -import javax.persistence.UniqueConstraint; +import javax.json.bind.annotation.JsonbTransient; +import javax.persistence.*; import javax.validation.constraints.NotNull; @Entity @@ -48,4 +42,8 @@ public class Hook extends PanacheEntityBase { @NotNull public boolean active; + + @ManyToOne(fetch = FetchType.LAZY) + @JsonbTransient + public Test test; } diff --git a/src/main/resources/db/changeLog.xml b/src/main/resources/db/changeLog.xml index 65fc55819..22c4cdf5f 100644 --- a/src/main/resources/db/changeLog.xml +++ b/src/main/resources/db/changeLog.xml @@ -1092,5 +1092,13 @@ + + + + + + + + From 89e0bbcfa8c72dc62687561df1e388757983396f Mon Sep 17 00:00:00 2001 From: John OHara Date: Mon, 30 Nov 2020 13:06:37 +0000 Subject: [PATCH 08/13] Define webhooks for individual test definitions --- .../tools/horreum/api/AlertingService.java | 6 +- .../tools/horreum/api/HookService.java | 14 ++ .../tools/horreum/api/TestService.java | 10 +- .../tools/horreum/entity/json/Hook.java | 3 - src/main/resources/db/changeLog.xml | 7 - webapp/src/App.tsx | 2 +- webapp/src/domain/hooks/AllHooks.tsx | 3 +- webapp/src/domain/hooks/api.ts | 7 +- webapp/src/domain/tests/General.tsx | 176 ++++++++------- webapp/src/domain/tests/Hooks.tsx | 204 ++++++++++++++++++ webapp/src/domain/tests/Test.tsx | 21 +- webapp/src/domain/tests/actionTypes.ts | 1 + webapp/src/domain/tests/actions.ts | 33 ++- webapp/src/domain/tests/api.ts | 5 + webapp/src/domain/tests/reducers.ts | 13 +- webapp/src/index.css | 4 + 16 files changed, 407 insertions(+), 102 deletions(-) create mode 100644 webapp/src/domain/tests/Hooks.tsx diff --git a/src/main/java/io/hyperfoil/tools/horreum/api/AlertingService.java b/src/main/java/io/hyperfoil/tools/horreum/api/AlertingService.java index c917c5662..45c770dcc 100644 --- a/src/main/java/io/hyperfoil/tools/horreum/api/AlertingService.java +++ b/src/main/java/io/hyperfoil/tools/horreum/api/AlertingService.java @@ -474,12 +474,12 @@ public void afterCompletion(int status) { @PermitAll @GET @Path("variables") - public Response variables(@QueryParam("test") Integer testId) { + public List variables(@QueryParam("test") Integer testId) { try (@SuppressWarnings("unused") CloseMe closeMe = sqlService.withRoles(em, identity)) { if (testId != null) { - return Response.ok(Variable.list("testid", testId)).build(); + return Variable.list("testid", testId); } else { - return Response.ok(Variable.listAll()).build(); + return Variable.listAll(); } } } diff --git a/src/main/java/io/hyperfoil/tools/horreum/api/HookService.java b/src/main/java/io/hyperfoil/tools/horreum/api/HookService.java index 57308896b..d2ff7cc8c 100644 --- a/src/main/java/io/hyperfoil/tools/horreum/api/HookService.java +++ b/src/main/java/io/hyperfoil/tools/horreum/api/HookService.java @@ -2,6 +2,7 @@ import io.hyperfoil.tools.horreum.JsonAdapter; import io.hyperfoil.tools.horreum.entity.alerting.Change; +import io.hyperfoil.tools.horreum.entity.alerting.Variable; import io.hyperfoil.tools.horreum.entity.json.Hook; import io.hyperfoil.tools.horreum.entity.json.Run; import io.hyperfoil.tools.horreum.entity.json.Test; @@ -19,6 +20,7 @@ import org.jboss.logging.Logger; import javax.annotation.PostConstruct; +import javax.annotation.security.PermitAll; import javax.annotation.security.RolesAllowed; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; @@ -227,4 +229,16 @@ public List list(@QueryParam("limit") Integer limit, } } + @RolesAllowed(Roles.ADMIN) + @GET + @Path("test/{id}") + public List variables(@PathParam("id") Integer testId) { + try (@SuppressWarnings("unused") CloseMe closeMe = sqlService.withRoles(em, identity)) { + if (testId != null) { + return Hook.list("target", testId); + } else { + return Variable.listAll(); + } + } + } } diff --git a/src/main/java/io/hyperfoil/tools/horreum/api/TestService.java b/src/main/java/io/hyperfoil/tools/horreum/api/TestService.java index 255b701a2..e738f9fbf 100644 --- a/src/main/java/io/hyperfoil/tools/horreum/api/TestService.java +++ b/src/main/java/io/hyperfoil/tools/horreum/api/TestService.java @@ -270,13 +270,17 @@ public Response updateHook(@PathParam("testId") Integer testId, Hook hook) { if (test == null) { return Response.status(Response.Status.NOT_FOUND).build(); } -// hook.ensureLinked(); - hook.test = test; + hook.target = testId; if (hook.id == null) { em.persist(hook); } else { - em.merge(hook); + if (!hook.active) { + Hook toDelete = em.find(Hook.class, hook.id); + em.remove(toDelete); + } else { + em.merge(hook); + } } test.persist(); } diff --git a/src/main/java/io/hyperfoil/tools/horreum/entity/json/Hook.java b/src/main/java/io/hyperfoil/tools/horreum/entity/json/Hook.java index 5a0b32b6a..faf64666a 100644 --- a/src/main/java/io/hyperfoil/tools/horreum/entity/json/Hook.java +++ b/src/main/java/io/hyperfoil/tools/horreum/entity/json/Hook.java @@ -43,7 +43,4 @@ public class Hook extends PanacheEntityBase { @NotNull public boolean active; - @ManyToOne(fetch = FetchType.LAZY) - @JsonbTransient - public Test test; } diff --git a/src/main/resources/db/changeLog.xml b/src/main/resources/db/changeLog.xml index 22c4cdf5f..8abea1999 100644 --- a/src/main/resources/db/changeLog.xml +++ b/src/main/resources/db/changeLog.xml @@ -1093,12 +1093,5 @@ - - - - - - - diff --git a/webapp/src/App.tsx b/webapp/src/App.tsx index 652b60150..21ee00fe9 100644 --- a/webapp/src/App.tsx +++ b/webapp/src/App.tsx @@ -79,7 +79,7 @@ function Main() { { isAdmin && - WebHooks + Global WebHooks } diff --git a/webapp/src/domain/hooks/AllHooks.tsx b/webapp/src/domain/hooks/AllHooks.tsx index e38c60b92..70592bce8 100644 --- a/webapp/src/domain/hooks/AllHooks.tsx +++ b/webapp/src/domain/hooks/AllHooks.tsx @@ -10,7 +10,7 @@ import { PageSection, Toolbar, ToolbarContent, - ToolbarItem, + ToolbarItem, Alert, } from '@patternfly/react-core'; import { OutlinedTimesCircleIcon, @@ -82,6 +82,7 @@ export default ()=>{ },[dispatch, isAdmin]) return ( + setOpen(false)} onSubmit={(v)=>{setOpen(false); dispatch(add(v)); }} /> diff --git a/webapp/src/domain/hooks/api.ts b/webapp/src/domain/hooks/api.ts index 8578607f9..80765d4b0 100644 --- a/webapp/src/domain/hooks/api.ts +++ b/webapp/src/domain/hooks/api.ts @@ -6,6 +6,7 @@ const endPoints = { base: ()=>`${base}`, crud: (id: number)=> `${base}/${id}/`, list: ()=> `${base}/list/`, + testHooks: (id: number)=> `${base}/test/${id}`, } export const all = () => { @@ -20,4 +21,8 @@ export const get = (id: number) => { } export const remove = (id: number) => { return fetchApi(endPoints.crud(id),null,'delete'); -} \ No newline at end of file +} + +export const fetchHooks = (testId: number) => { + return fetchApi(endPoints.testHooks(testId), null, 'get') +} diff --git a/webapp/src/domain/tests/General.tsx b/webapp/src/domain/tests/General.tsx index 0d7cc7007..06647a3fa 100644 --- a/webapp/src/domain/tests/General.tsx +++ b/webapp/src/domain/tests/General.tsx @@ -1,24 +1,24 @@ -import React, { useState, useRef, useEffect } from 'react'; -import { useSelector, useDispatch } from 'react-redux' +import React, {useState, useRef, useEffect} from 'react'; +import {useSelector, useDispatch} from 'react-redux' import { Button, Form, - FormGroup, + FormGroup, Grid, GridItem, TextArea, TextInput, } from '@patternfly/react-core'; -import { sendTest } from './actions'; -import { alertAction, constraintValidationFormatter } from '../../alerts' +import {sendTest} from './actions'; +import {alertAction, constraintValidationFormatter} from '../../alerts' import AccessIcon from '../../components/AccessIcon' import AccessChoice from '../../components/AccessChoice' import Accessors from '../../components/Accessors' import OwnerSelect from '../../components/OwnerSelect' -import Editor, { ValueGetter } from '../../components/Editor/monaco/Editor' +import Editor, {ValueGetter} from '../../components/Editor/monaco/Editor' -import { Test, TestDispatch } from './reducers'; +import {Test, TestDispatch} from './reducers'; import { useTester, @@ -27,16 +27,16 @@ import { defaultRoleSelector } from '../../auth' -import { TabFunctionsRef } from './Test' +import {TabFunctionsRef} from './Test' type GeneralProps = { test?: Test, onTestIdChange(id: number): void, onModified(modified: boolean): void, funcsRef: TabFunctionsRef - } +} -export default ({ test, onTestIdChange, onModified, funcsRef }: GeneralProps) => { +export default ({test, onTestIdChange, onModified, funcsRef}: GeneralProps) => { const defaultRole = useSelector(defaultRoleSelector) const [name, setName] = useState(""); const [description, setDescription] = useState(""); @@ -95,89 +95,111 @@ export default ({ test, onTestIdChange, onModified, funcsRef }: GeneralProps) => const isTester = useTester(owner) return (<> -
- - 0 ? "default" : "error"} - onChange={n => { - setName(n) - onModified(true) - }} - /> - - -