From e873f694d6a99d13e305d2107482c476164ad9dd Mon Sep 17 00:00:00 2001 From: Chris Rohr <51920+chrisrohr@users.noreply.github.com> Date: Thu, 14 Sep 2023 14:53:15 -0400 Subject: [PATCH] Add System management page (#351) * Add System management page * Add more checks to satisfy code coverage * Fix type of wrong word in docs --- .../java/org/kiwiproject/champagne/App.java | 2 +- .../champagne/dao/DeployableSystemDao.java | 6 +- .../dao/DeploymentEnvironmentDao.java | 7 +- .../dao/mappers/SystemUserMapper.java | 10 +- .../champagne/model/DeployableSystem.java | 18 +- .../resource/DeployableSystemResource.java | 36 +++- .../dao/DeploymentEnvironmentDaoTest.java | 21 +- .../DeployableSystemResourceTest.java | 18 +- ui/src/components/SideBar/SideBar.vue | 2 +- ui/src/router/index.js | 2 + ui/src/views/ChampagneAdmin/SystemsView.vue | 204 ++++++++++++++++++ 11 files changed, 300 insertions(+), 26 deletions(-) create mode 100644 ui/src/views/ChampagneAdmin/SystemsView.vue diff --git a/service/src/main/java/org/kiwiproject/champagne/App.java b/service/src/main/java/org/kiwiproject/champagne/App.java index 6584792a..bfc19f3b 100644 --- a/service/src/main/java/org/kiwiproject/champagne/App.java +++ b/service/src/main/java/org/kiwiproject/champagne/App.java @@ -121,7 +121,7 @@ public void run(AppConfig configuration, Environment environment) { environment.jersey().register(new TaskResource(releaseDao, releaseStatusDao, taskDao, taskStatusDao, deploymentEnvironmentDao, auditRecordDao, errorDao)); environment.jersey().register(new UserResource(userDao, deployableSystemDao, auditRecordDao, errorDao)); environment.jersey().register(new ApplicationErrorResource(errorDao)); - environment.jersey().register(new DeployableSystemResource(deployableSystemDao, userDao, auditRecordDao, errorDao)); + environment.jersey().register(new DeployableSystemResource(deployableSystemDao, userDao, deploymentEnvironmentDao, auditRecordDao, errorDao)); environment.jersey().register(new TagResource(tagDao, auditRecordDao, errorDao)); environment.jersey().register(new MetricsResource(buildDao)); diff --git a/service/src/main/java/org/kiwiproject/champagne/dao/DeployableSystemDao.java b/service/src/main/java/org/kiwiproject/champagne/dao/DeployableSystemDao.java index c35e2372..ea34d28d 100644 --- a/service/src/main/java/org/kiwiproject/champagne/dao/DeployableSystemDao.java +++ b/service/src/main/java/org/kiwiproject/champagne/dao/DeployableSystemDao.java @@ -1,5 +1,7 @@ package org.kiwiproject.champagne.dao; +import java.util.List; + import org.apache.commons.lang3.BooleanUtils; import org.jdbi.v3.sqlobject.config.RegisterRowMapper; import org.jdbi.v3.sqlobject.customizer.Bind; @@ -12,8 +14,6 @@ import org.kiwiproject.champagne.dao.mappers.SystemUserMapper; import org.kiwiproject.champagne.model.DeployableSystem; -import java.util.List; - public interface DeployableSystemDao { @SqlUpdate("insert into deployable_systems (name) values (:name)") @@ -31,7 +31,7 @@ public interface DeployableSystemDao { @SqlQuery("select count(*) from deployable_systems") long countDeployableSystems(); - @SqlQuery("select * from users_deployable_systems where deployable_system_id = :systemId") + @SqlQuery("select u.id, u.display_name, u.system_identifier, usd.system_admin from users u left join users_deployable_systems usd on u.id = usd.user_id where usd.deployable_system_id = :systemId") @RegisterRowMapper(SystemUserMapper.class) List findUsersForSystem(@Bind("systemId") long systemId); diff --git a/service/src/main/java/org/kiwiproject/champagne/dao/DeploymentEnvironmentDao.java b/service/src/main/java/org/kiwiproject/champagne/dao/DeploymentEnvironmentDao.java index a6a6dbfe..df8864f2 100644 --- a/service/src/main/java/org/kiwiproject/champagne/dao/DeploymentEnvironmentDao.java +++ b/service/src/main/java/org/kiwiproject/champagne/dao/DeploymentEnvironmentDao.java @@ -1,5 +1,7 @@ package org.kiwiproject.champagne.dao; +import java.util.List; + import org.jdbi.v3.sqlobject.config.RegisterRowMapper; import org.jdbi.v3.sqlobject.customizer.Bind; import org.jdbi.v3.sqlobject.customizer.BindBean; @@ -9,8 +11,6 @@ import org.kiwiproject.champagne.dao.mappers.DeploymentEnvironmentMapper; import org.kiwiproject.champagne.model.DeploymentEnvironment; -import java.util.List; - @RegisterRowMapper(DeploymentEnvironmentMapper.class) public interface DeploymentEnvironmentDao { @SqlUpdate("insert into deployment_environments (environment_name, deployable_system_id) values (:name, :deployableSystemId)") @@ -32,4 +32,7 @@ public interface DeploymentEnvironmentDao { @SqlUpdate("update deployment_environments set deleted = false, updated_at = current_timestamp where id = :id") int unSoftDeleteById(@Bind("id") long id); + @SqlQuery("select environment_name from deployment_environments where id = :id") + String getEnvironmentName(@Bind("id") long id); + } diff --git a/service/src/main/java/org/kiwiproject/champagne/dao/mappers/SystemUserMapper.java b/service/src/main/java/org/kiwiproject/champagne/dao/mappers/SystemUserMapper.java index c4e89bf5..2b64def3 100644 --- a/service/src/main/java/org/kiwiproject/champagne/dao/mappers/SystemUserMapper.java +++ b/service/src/main/java/org/kiwiproject/champagne/dao/mappers/SystemUserMapper.java @@ -1,18 +1,20 @@ package org.kiwiproject.champagne.dao.mappers; +import java.sql.ResultSet; +import java.sql.SQLException; + import org.jdbi.v3.core.mapper.RowMapper; import org.jdbi.v3.core.statement.StatementContext; import org.kiwiproject.champagne.model.DeployableSystem; -import java.sql.ResultSet; -import java.sql.SQLException; - public class SystemUserMapper implements RowMapper { @Override public DeployableSystem.SystemUser map(ResultSet rs, StatementContext ctx) throws SQLException { return DeployableSystem.SystemUser.builder() - .userId(rs.getLong("user_id")) + .userId(rs.getLong("id")) + .displayName(rs.getString("display_name")) + .systemIdentifier(rs.getString("system_identifier")) .admin(rs.getBoolean("system_admin")) .build(); } diff --git a/service/src/main/java/org/kiwiproject/champagne/model/DeployableSystem.java b/service/src/main/java/org/kiwiproject/champagne/model/DeployableSystem.java index e9ae97dc..5e10961c 100644 --- a/service/src/main/java/org/kiwiproject/champagne/model/DeployableSystem.java +++ b/service/src/main/java/org/kiwiproject/champagne/model/DeployableSystem.java @@ -25,24 +25,38 @@ public class DeployableSystem { String environmentPromotionOrder; /** - * This is a transitive property populated through the users_system table and indicates if a specific user is + * This is a transient property populated through the users_system table and indicates if a specific user is * an admin in this system. Listing all systems will NOT populate this field. */ @With boolean admin; /** - * Transitive property that is the list of users assigned to this system and if they are an admin + * Transient property that is the list of users assigned to this system and if they are an admin */ @With List users; + /** + * Transient property that is the resolved development environment name. + */ + @With + String devEnvName; + + /** + * Transient property that is the list of the resolved deployment environment names in their promotion order. + */ + @With + List envOrder; + @Builder @Value @JsonIgnoreProperties(ignoreUnknown = true) @ToString public static class SystemUser { Long userId; + String displayName; + String systemIdentifier; boolean admin; } } diff --git a/service/src/main/java/org/kiwiproject/champagne/resource/DeployableSystemResource.java b/service/src/main/java/org/kiwiproject/champagne/resource/DeployableSystemResource.java index a620f325..79fd6610 100644 --- a/service/src/main/java/org/kiwiproject/champagne/resource/DeployableSystemResource.java +++ b/service/src/main/java/org/kiwiproject/champagne/resource/DeployableSystemResource.java @@ -1,12 +1,15 @@ package org.kiwiproject.champagne.resource; import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.kiwiproject.champagne.model.AuditRecord.Action.CREATED; import static org.kiwiproject.champagne.model.AuditRecord.Action.DELETED; import static org.kiwiproject.champagne.model.AuditRecord.Action.UPDATED; import static org.kiwiproject.search.KiwiSearching.zeroBasedOffset; +import java.util.Arrays; import java.util.List; +import java.util.Objects; import com.codahale.metrics.annotation.ExceptionMetered; import com.codahale.metrics.annotation.Timed; @@ -29,6 +32,7 @@ import org.dhatim.dropwizard.jwt.cookie.authentication.DefaultJwtCookiePrincipal; import org.kiwiproject.champagne.dao.AuditRecordDao; import org.kiwiproject.champagne.dao.DeployableSystemDao; +import org.kiwiproject.champagne.dao.DeploymentEnvironmentDao; import org.kiwiproject.champagne.dao.UserDao; import org.kiwiproject.champagne.model.DeployableSystem; import org.kiwiproject.champagne.model.DeployableSystem.SystemUser; @@ -44,12 +48,18 @@ public class DeployableSystemResource extends AuditableResource { private final DeployableSystemDao deployableSystemDao; private final UserDao userDao; + private final DeploymentEnvironmentDao deploymentEnvironmentDao; + + public DeployableSystemResource(DeployableSystemDao deployableSystemDao, + UserDao userDao, DeploymentEnvironmentDao deploymentEnvironmentDao, + AuditRecordDao auditRecordDao, + ApplicationErrorDao errorDao) { - public DeployableSystemResource(DeployableSystemDao deployableSystemDao, UserDao userDao, AuditRecordDao auditRecordDao, ApplicationErrorDao errorDao) { super(auditRecordDao, errorDao); this.deployableSystemDao = deployableSystemDao; this.userDao = userDao; + this.deploymentEnvironmentDao = deploymentEnvironmentDao; } @GET @@ -75,7 +85,7 @@ public Response listAllSystems(@QueryParam("pageNumber") @DefaultValue("1") int var offset = zeroBasedOffset(pageNumber, pageSize); var systems = deployableSystemDao.findPagedDeployableSystems(offset, pageSize).stream() - .map(system -> system.withUsers(deployableSystemDao.findUsersForSystem(system.getId()))) + .map(this::inflateSystem) .toList(); var total = deployableSystemDao.countDeployableSystems(); @@ -84,6 +94,25 @@ public Response listAllSystems(@QueryParam("pageNumber") @DefaultValue("1") int return Response.ok(page).build(); } + private DeployableSystem inflateSystem(DeployableSystem system) { + var updatedSystem = system.withUsers(deployableSystemDao.findUsersForSystem(system.getId())); + + if (Objects.nonNull(system.getDevEnvironmentId())) { + updatedSystem = updatedSystem.withDevEnvName(deploymentEnvironmentDao.getEnvironmentName(system.getDevEnvironmentId())); + } + + if (isNotBlank(system.getEnvironmentPromotionOrder())) { + var orderedEnvNames = Arrays.stream(system.getEnvironmentPromotionOrder().split(",")) + .map(Long::parseLong) + .map(deploymentEnvironmentDao::getEnvironmentName) + .toList(); + + updatedSystem = updatedSystem.withEnvOrder(orderedEnvNames); + } + + return updatedSystem; + } + @POST @Timed @ExceptionMetered @@ -176,7 +205,4 @@ public Response removeUserFromSystem(@PathParam("systemId") long systemId, @Path deployableSystemDao.deleteUserFromSystem(systemId, userId); return Response.noContent().build(); } - - // TODO: Add endpoint to set and unset existing system user as admin (only can be done by system admins) - // TODO: Add endpoint to retrieve a list of users for the current system (only by system admin) } diff --git a/service/src/test/java/org/kiwiproject/champagne/dao/DeploymentEnvironmentDaoTest.java b/service/src/test/java/org/kiwiproject/champagne/dao/DeploymentEnvironmentDaoTest.java index 19b81352..ac581c19 100644 --- a/service/src/test/java/org/kiwiproject/champagne/dao/DeploymentEnvironmentDaoTest.java +++ b/service/src/test/java/org/kiwiproject/champagne/dao/DeploymentEnvironmentDaoTest.java @@ -6,6 +6,9 @@ import static org.kiwiproject.collect.KiwiLists.first; import static org.kiwiproject.test.util.DateTimeTestHelper.assertTimeDifferenceWithinTolerance; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; + import org.jdbi.v3.core.Handle; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -17,9 +20,6 @@ import org.kiwiproject.test.junit.jupiter.Jdbi3DaoExtension; import org.kiwiproject.test.junit.jupiter.PostgresLiquibaseTestExtension; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; - @DisplayName("DeploymentEnvironmentDao") class DeploymentEnvironmentDaoTest { @@ -71,7 +71,7 @@ void shouldInsertUserSuccessfully() { } @Nested - class UpdateUser { + class UpdateEnv { @Test void shouldUpdateDeploymentEnvironmentSuccessfully() { @@ -117,6 +117,19 @@ void shouldReturnEmptyListWhenNoDeploymentEnvironmentsFound() { } } + @Nested + class GetEnvironmentName { + + @Test + void shouldReturnNameOfGivenEnvironment() { + var systemId = insertDeployableSystem(handle, "kiwi"); + var envId = insertDeploymentEnvironmentRecord(handle, "DEV", systemId); + + var name = dao.getEnvironmentName(envId); + assertThat(name).isEqualTo("DEV"); + } + } + @Nested class HardDeleteById { diff --git a/service/src/test/java/org/kiwiproject/champagne/resource/DeployableSystemResourceTest.java b/service/src/test/java/org/kiwiproject/champagne/resource/DeployableSystemResourceTest.java index 8b9fd816..99e341ce 100644 --- a/service/src/test/java/org/kiwiproject/champagne/resource/DeployableSystemResourceTest.java +++ b/service/src/test/java/org/kiwiproject/champagne/resource/DeployableSystemResourceTest.java @@ -10,6 +10,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -28,6 +29,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.kiwiproject.champagne.dao.AuditRecordDao; import org.kiwiproject.champagne.dao.DeployableSystemDao; +import org.kiwiproject.champagne.dao.DeploymentEnvironmentDao; import org.kiwiproject.champagne.dao.UserDao; import org.kiwiproject.champagne.model.AuditRecord; import org.kiwiproject.champagne.model.DeployableSystem; @@ -45,13 +47,14 @@ class DeployableSystemResourceTest { private static final AuditRecordDao AUDIT_RECORD_DAO = mock(AuditRecordDao.class); private static final ApplicationErrorDao APPLICATION_ERROR_DAO = mock(ApplicationErrorDao.class); private static final UserDao USER_DAO = mock(UserDao.class); + private static final DeploymentEnvironmentDao DEPLOYMENT_ENVIRONMENT_DAO = mock(DeploymentEnvironmentDao.class); - private static final DeployableSystemResource RESOURCE = new DeployableSystemResource(DEPLOYABLE_SYSTEM_DAO, USER_DAO, AUDIT_RECORD_DAO, APPLICATION_ERROR_DAO); + private static final DeployableSystemResource RESOURCE = new DeployableSystemResource(DEPLOYABLE_SYSTEM_DAO, USER_DAO, DEPLOYMENT_ENVIRONMENT_DAO, AUDIT_RECORD_DAO, APPLICATION_ERROR_DAO); private static final ResourceExtension RESOURCES = JwtResourceHelper.configureJwtResource(RESOURCE); @AfterEach void cleanup() { - reset(DEPLOYABLE_SYSTEM_DAO, AUDIT_RECORD_DAO, USER_DAO); + reset(DEPLOYABLE_SYSTEM_DAO, AUDIT_RECORD_DAO, USER_DAO, DEPLOYMENT_ENVIRONMENT_DAO); } @Nested @@ -131,12 +134,17 @@ void shouldReturnPagedListOfDeployableSystems() { var systemUser = DeployableSystem.SystemUser.builder() .userId(1L) + .displayName("Jim Bob") + .systemIdentifier("jbob") .admin(true) .build(); when(DEPLOYABLE_SYSTEM_DAO.findPagedDeployableSystems(0, 10)).thenReturn(List.of(system)); when(DEPLOYABLE_SYSTEM_DAO.countDeployableSystems()).thenReturn(1L); when(DEPLOYABLE_SYSTEM_DAO.findUsersForSystem(1L)).thenReturn(List.of(systemUser)); + when(DEPLOYMENT_ENVIRONMENT_DAO.getEnvironmentName(2L)).thenReturn("dev"); + when(DEPLOYMENT_ENVIRONMENT_DAO.getEnvironmentName(3L)).thenReturn("test"); + when(DEPLOYMENT_ENVIRONMENT_DAO.getEnvironmentName(4L)).thenReturn("prod"); var token = generateJwt(true); var response = RESOURCES.client() @@ -158,6 +166,9 @@ void shouldReturnPagedListOfDeployableSystems() { verify(DEPLOYABLE_SYSTEM_DAO).findPagedDeployableSystems(0, 10); verify(DEPLOYABLE_SYSTEM_DAO).countDeployableSystems(); verify(DEPLOYABLE_SYSTEM_DAO).findUsersForSystem(1L); + verify(DEPLOYMENT_ENVIRONMENT_DAO, times(2)).getEnvironmentName(2L); + verify(DEPLOYMENT_ENVIRONMENT_DAO).getEnvironmentName(3L); + verify(DEPLOYMENT_ENVIRONMENT_DAO).getEnvironmentName(4L); verifyNoMoreInteractions(DEPLOYABLE_SYSTEM_DAO); } @@ -167,8 +178,6 @@ void shouldReturnPagedListOfReleasesWithDefaultPaging() { var system = DeployableSystem.builder() .id(1L) .name("kiwi") - .devEnvironmentId(2L) - .environmentPromotionOrder("2,3,4") .build(); var systemUser = DeployableSystem.SystemUser.builder() @@ -200,6 +209,7 @@ void shouldReturnPagedListOfReleasesWithDefaultPaging() { verify(DEPLOYABLE_SYSTEM_DAO).findUsersForSystem(1L); verifyNoMoreInteractions(DEPLOYABLE_SYSTEM_DAO); + verifyNoInteractions(DEPLOYMENT_ENVIRONMENT_DAO); } } diff --git a/ui/src/components/SideBar/SideBar.vue b/ui/src/components/SideBar/SideBar.vue index 5c16b227..9b2888a0 100644 --- a/ui/src/components/SideBar/SideBar.vue +++ b/ui/src/components/SideBar/SideBar.vue @@ -303,7 +303,7 @@
  • - + +
    +
    + + + + + + + + + + + + + +
    + + + + + +
    +
    +
    +
    +
    + Add new system +
    +
    +
    +
    + +
    +
    + Name is required! +
    +
    +
    + + +
    +
    +
    +
    +
    +
    + + + + +