Skip to content

Commit

Permalink
[Unités] Ajouter un flag "Adresse de diffusion de l'unité" sur les co…
Browse files Browse the repository at this point in the history
…ntacts unité (#1338)

## Related Pull Requests & Issues

- Resolve #1271 

Reviewable, only tasks left with @AdelineCelier:

- [x] Ask for new broadcast/subscription Icon and add it to monitor-ui.
- [x] Check if saving should be instant when subscribing/unsubscribing
or once submit button is clicked (current state)
=> UX a bit weird with warning message + banner before saving the form?
- [x] Double-check buttons title

----

- [x] Tests E2E (Cypress)
  • Loading branch information
ivangabriele committed May 8, 2024
2 parents f55fcb5 + ceb3fe8 commit 7ee1ed4
Show file tree
Hide file tree
Showing 133 changed files with 5,140 additions and 1,925 deletions.
55 changes: 44 additions & 11 deletions .github/workflows/cicd-app.yml
Expand Up @@ -31,7 +31,7 @@ jobs:
run: echo "ENV_PROFILE=dev" >> $GITHUB_ENV

- name: Set ENV profile as PROD when it is a release
if: startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/heads/v')
if: startsWith(github.ref, 'refs/tags/v1') || startsWith(github.ref, 'refs/heads/v1') || startsWith(github.ref, 'refs/tags/v2') || startsWith(github.ref, 'refs/heads/v2')
run: echo "ENV_PROFILE=prod" >> $GITHUB_ENV

- id: env_profile
Expand All @@ -54,27 +54,40 @@ jobs:
echo $VERSION
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
build:
name: Run unit tests, build and package
unit_test_backend:
name: Run backend unit tests
needs: version
runs-on: ubuntu-22.04
env:
ENV_PROFILE: ${{needs.version.outputs.ENV_PROFILE}}
VERSION: ${{needs.version.outputs.VERSION}}
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
PUPPETEER_SKIP_DOWNLOAD: "true"
steps:
- name: Show version
run: echo "VERSION:${{ env.VERSION }} ENV_PROFILE:${{ env.ENV_PROFILE }}"

- name: Checkout
uses: actions/checkout@v4

- name: Setup Java JDK
uses: actions/setup-java@v4.2.1
with:
# https://github.com/actions/setup-java/blob/main/README.md#Supported-distributions
distribution: zulu
java-version: 17
distribution: "dragonwell"

- name: Checkout
uses: actions/checkout@v4

- name: Unit test
run: make test-back

unit_test_frontend:
name: Run frontend unit tests
needs: version
runs-on: ubuntu-22.04
env:
PUPPETEER_SKIP_DOWNLOAD: "true"
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
Expand All @@ -96,7 +109,27 @@ jobs:
working-directory: ./frontend

- name: Unit test
run: make test
run: npm run test:unit --coverage
working-directory: ./frontend

- name: Upload coverage
run: npx codecov
working-directory: ./frontend

build:
name: Build and package
needs: version
runs-on: ubuntu-22.04
env:
ENV_PROFILE: ${{needs.version.outputs.ENV_PROFILE}}
VERSION: ${{needs.version.outputs.VERSION}}
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
steps:
- name: Show version
run: echo "VERSION:${{ env.VERSION }} ENV_PROFILE:${{ env.ENV_PROFILE }}"

- name: Checkout
uses: actions/checkout@v4

- name: Set up Docker Buildx
id: buildx
Expand Down Expand Up @@ -257,8 +290,8 @@ jobs:

push_to_registry:
name: Push to registry
needs: [version, e2e_test]
# needs: [version, e2e_test, e2e_multi_windows_test]
needs: [version, unit_test_backend, unit_test_frontend, e2e_test]
# needs: [version, e2e_test, e2e_multi_windows_test, unit_test_frontend, e2e_test]
runs-on: ubuntu-22.04
if: startsWith(github.ref, 'refs/heads/dependabot') == false
env:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/cicd-datapipeline.yml
Expand Up @@ -31,7 +31,7 @@ jobs:
repository: mtes-mct/monitorenv

- name: Set ENV_PROFILE as PROD when it is a release
if: startsWith(github.ref, 'refs/tags/v')
if: startsWith(github.ref, 'refs/tags/v1') || startsWith(github.ref, 'refs/heads/v1') || startsWith(github.ref, 'refs/tags/v2') || startsWith(github.ref, 'refs/heads/v2')
run: echo "ENV_PROFILE=prod" >> $GITHUB_ENV

- name: Set VERSION
Expand Down

This file was deleted.

Expand Up @@ -4,6 +4,8 @@ data class ControlUnitContactEntity(
val id: Int? = null,
val controlUnitId: Int,
val email: String? = null,
val isEmailSubscriptionContact: Boolean,
val isSmsSubscriptionContact: Boolean,
val name: String,
val phone: String? = null,
)
@@ -0,0 +1,25 @@
package fr.gouv.cacem.monitorenv.domain.exceptions

import org.slf4j.Logger
import org.slf4j.LoggerFactory

/**
* Domain exception to throw when an internal error occurred in the backend.
*
* ## Examples
* - An unexpected exception has been caught.
*
* ## Logging
* This exception is logged as an error on the Backend side.
*/
open class BackendInternalException(
final override val message: String,
val originalException: Exception? = null,
) : Throwable(message) {
private val logger: Logger = LoggerFactory.getLogger(BackendInternalException::class.java)

init {
logger.error("BackendInternalException: $message")
originalException?.let { logger.error("${it::class.simpleName}: ${it.message}") }
}
}
@@ -0,0 +1,32 @@
package fr.gouv.cacem.monitorenv.domain.exceptions

/**
* Error code thrown when the request is valid but the backend cannot process it.
*
* It's called "usage" because this request likely comes from an end-user action that's no longer valid
* which happens when their client data is not up-to-date with the backend.
*
* But it can also be a Frontend side bug.
*
* ## Examples
* - A user tries to create a resource that has already been created.
* - A user tries to delete a resource that doesn't exist anymore.
*
* ## Logging
* The related exception is NOT logged on the Backend side.
* It should be logged on the Frontend side IF it's unexpected (= Frontend bug),
* it should rather display a comprehensible error message to the end-user.
*
* ### ⚠️ Important
* **Don't forget to mirror any update here in the corresponding Frontend enum.**
*/
enum class BackendUsageErrorCode {
/** Thrown when attempting to attach a mission to a reporting that has already a mission attached. */
CHILD_ALREADY_ATTACHED,

/** Thrown when attempting to archive an entity linked to non-archived child(ren). */
UNARCHIVED_CHILD,

/** Thrown when attempting to delete a mission that has actions created by other applications. */
EXISTING_MISSION_ACTION,
}
@@ -1,5 +1,24 @@
package fr.gouv.cacem.monitorenv.domain.exceptions

import fr.gouv.cacem.monitorenv.domain.entities.ErrorCode

class BackendUsageException(val code: ErrorCode, val data: Any) : Throwable(code.name)
/**
* Domain exception to throw when a request is valid but the backend cannot process it.
*
* It's called "usage" because this request likely comes from an end-user action that's no longer valid
* which happens when their client data is not up-to-date with the backend.
*
* But it can also be a Frontend side bug.
*
* ## Examples
* - A user tries to create a resource that has already been created.
* - A user tries to delete a resource that doesn't exist anymore.
*
* ## Logging
* This exception is NOT logged on the Backend side.
* It should be logged on the Frontend side IF it's unexpected (= Frontend bug),
* it should rather display a comprehensible error message to the end-user.
*/
open class BackendUsageException(
val code: BackendUsageErrorCode,
final override val message: String? = null,
val data: Any? = null,
) : Throwable(code.name)

This file was deleted.

This file was deleted.

@@ -1,3 +1,4 @@
package fr.gouv.cacem.monitorenv.domain.exceptions

@Deprecated("Use `BackendUsageException` instead.")
class CouldNotDeleteException(message: String) : RuntimeException(message)

This file was deleted.

@@ -1,4 +1,5 @@
package fr.gouv.cacem.monitorenv.domain.exceptions

@Deprecated("Use `BackendUsageException` with the right code instead (depending on the case).")
class EntityConversionException(message: String, cause: Throwable? = null) :
Throwable(message, cause)
@@ -1,4 +1,5 @@
package fr.gouv.cacem.monitorenv.domain.exceptions

@Deprecated("Use `BackendUsageException` instead.")
class NotFoundException(message: String, cause: Throwable? = null) :
Throwable(message, cause)
@@ -1,4 +1,5 @@
package fr.gouv.cacem.monitorenv.domain.exceptions

/** Thrown when attempting to attach a reporting already attached to a mission. */
@Deprecated("Use `BackendUsageException` instead.")
class ReportingAlreadyAttachedException(message: String) : RuntimeException(message)
@@ -1,7 +1,8 @@
package fr.gouv.cacem.monitorenv.domain.use_cases.administration

import fr.gouv.cacem.monitorenv.config.UseCase
import fr.gouv.cacem.monitorenv.domain.exceptions.CouldNotArchiveException
import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageErrorCode
import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageException
import fr.gouv.cacem.monitorenv.domain.repositories.IAdministrationRepository

@UseCase
Expand All @@ -11,7 +12,8 @@ class ArchiveAdministration(
) {
fun execute(administrationId: Int) {
if (!canArchiveAdministration.execute(administrationId)) {
throw CouldNotArchiveException(
throw BackendUsageException(
BackendUsageErrorCode.UNARCHIVED_CHILD,
"Cannot archive administration (ID=$administrationId) due to some of its control units not being archived.",
)
}
Expand Down
Expand Up @@ -3,10 +3,55 @@ package fr.gouv.cacem.monitorenv.domain.use_cases.controlUnit
import fr.gouv.cacem.monitorenv.config.UseCase
import fr.gouv.cacem.monitorenv.domain.entities.controlUnit.ControlUnitContactEntity
import fr.gouv.cacem.monitorenv.domain.repositories.IControlUnitContactRepository
import fr.gouv.cacem.monitorenv.domain.repositories.IControlUnitRepository

@UseCase
class CreateOrUpdateControlUnitContact(private val controlUnitContactRepository: IControlUnitContactRepository) {
class CreateOrUpdateControlUnitContact(
private val controlUnitRepository: IControlUnitRepository,
private val controlUnitContactRepository: IControlUnitContactRepository,
) {
fun execute(controlUnitContact: ControlUnitContactEntity): ControlUnitContactEntity {
return controlUnitContactRepository.save(controlUnitContact)
val validControlUnitContact = validateSubscriptions(controlUnitContact)

val createdOrUpdatedControlUnitContact = controlUnitContactRepository.save(validControlUnitContact)

// There can only be one contact per control unit with an email subscription
if (controlUnitContact.id != null && controlUnitContact.isEmailSubscriptionContact) {
unsubscribeOtherContactsFromEmail(controlUnitContact.controlUnitId, controlUnitContact.id)
}

return createdOrUpdatedControlUnitContact
}

private fun unsubscribeOtherContactsFromEmail(controlUnitId: Int, controlUnitContactId: Int) {
val fullControlUnit = controlUnitRepository.findById(controlUnitId)
val otherContactsWithEmailSubscription = fullControlUnit.controlUnitContacts
// Filter and not find in the spirit of defensive programming
.filter { it.id != controlUnitContactId && it.isEmailSubscriptionContact }

otherContactsWithEmailSubscription.forEach {
val updatedControlUnitContact = it.copy(isEmailSubscriptionContact = false)

controlUnitContactRepository.save(updatedControlUnitContact)
}
}

/**
* If the contact is subscribed to emails/sms but has no email/phone, we unsubscribe them from emails/sms.
*/
private fun validateSubscriptions(controlUnitContact: ControlUnitContactEntity): ControlUnitContactEntity {
return controlUnitContact.copy(
isEmailSubscriptionContact = if (controlUnitContact.isEmailSubscriptionContact && controlUnitContact.email == null) {
false
} else {
controlUnitContact.isEmailSubscriptionContact
},

isSmsSubscriptionContact = if (controlUnitContact.isSmsSubscriptionContact && controlUnitContact.phone == null) {
false
} else {
controlUnitContact.isSmsSubscriptionContact
},
)
}
}
Expand Up @@ -3,14 +3,14 @@
package fr.gouv.cacem.monitorenv.domain.use_cases.missions

import fr.gouv.cacem.monitorenv.config.UseCase
import fr.gouv.cacem.monitorenv.domain.entities.ErrorCode
import fr.gouv.cacem.monitorenv.domain.entities.mission.MissionSourceEnum
import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageErrorCode
import fr.gouv.cacem.monitorenv.domain.exceptions.BackendUsageException
import fr.gouv.cacem.monitorenv.domain.repositories.IMissionRepository
import fr.gouv.cacem.monitorenv.domain.repositories.IReportingRepository
import org.slf4j.LoggerFactory
import java.time.ZonedDateTime
import java.util.UUID
import java.util.*

@UseCase
class DeleteMission(
Expand Down Expand Up @@ -40,8 +40,8 @@ class DeleteMission(
}

throw BackendUsageException(
ErrorCode.EXISTING_MISSION_ACTION,
errorSources,
code = BackendUsageErrorCode.EXISTING_MISSION_ACTION,
data = errorSources,
)
}

Expand Down

0 comments on commit 7ee1ed4

Please sign in to comment.