From 568810b54b9aba48a7fc2496420600b72c406f45 Mon Sep 17 00:00:00 2001 From: afwilcox Date: Thu, 12 Dec 2024 12:41:35 -0800 Subject: [PATCH 01/28] chore: update 0.6.9 main (#821) Co-authored-by: jon-funk Co-authored-by: Derek Roberts --- .github/workflows/pr-close.yml | 4 ++-- .github/workflows/release-main.yml | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pr-close.yml b/.github/workflows/pr-close.yml index 519709f4c..a0b95c61c 100644 --- a/.github/workflows/pr-close.yml +++ b/.github/workflows/pr-close.yml @@ -38,7 +38,7 @@ jobs: - uses: actions/checkout@v4 - run: ./.github/scripts/cleanup_pvcs.sh env: - OC_NAMESPACE: ${{ vars.OC_NAMESPACE }} + OC_NAMESPACE: ${{ secrets.OC_NAMESPACE }} OC_SERVER: ${{ vars.OC_SERVER }} OC_TOKEN: ${{ secrets.OC_TOKEN }} - PR_NUMBER: ${{ github.event.number }} \ No newline at end of file + PR_NUMBER: ${{ github.event.number }} diff --git a/.github/workflows/release-main.yml b/.github/workflows/release-main.yml index 0a1ac027b..70d89a78a 100644 --- a/.github/workflows/release-main.yml +++ b/.github/workflows/release-main.yml @@ -71,7 +71,7 @@ jobs: uses: bcgov/quickstart-openshift-helpers/.github/workflows/.deployer.yml@v0.8.3 secrets: oc_namespace: ${{ secrets.OC_NAMESPACE }} - oc_token: ${{ secrets.OC_TOKEN }} + oc_token: ${{ secrets.OC_BEARER_TOKEN }} with: environment: prod tag: ${{ needs.vars.outputs.pr }} @@ -84,8 +84,6 @@ jobs: --set webeoc.pdb.enabled=true --set nats.config.cluster.replicas=3 --set nats.config.cluster.enabled=true - --set bitnami-pg.backup.cronjob.storage.size=512Mi - --set bitnami-pg.primary.persistence.size=512Mi --set backup.enabled=true --set backup.persistence.size=256Mi From f4d5997efe733d196a549e613f2c262877ab56a7 Mon Sep 17 00:00:00 2001 From: Ryan Rondeau Date: Thu, 12 Dec 2024 13:29:56 -0800 Subject: [PATCH 02/28] fix: Map Search Filters / Officers (#817) Co-authored-by: afwilcox --- backend/src/v1/complaint/complaint.service.ts | 49 ++++++++++--------- backend/src/v1/officer/officer.service.ts | 4 +- .../complaints/complaint-filter.tsx | 1 + ...plaint-map-with-server-side-clustering.tsx | 14 ++++-- .../mapping/complaint-summary-popup.tsx | 4 +- 5 files changed, 42 insertions(+), 30 deletions(-) diff --git a/backend/src/v1/complaint/complaint.service.ts b/backend/src/v1/complaint/complaint.service.ts index d3e36eef1..7216db94d 100644 --- a/backend/src/v1/complaint/complaint.service.ts +++ b/backend/src/v1/complaint/complaint.service.ts @@ -434,8 +434,28 @@ export class ComplaintService { query: string, token: string, ): Promise> { + let caseSearchData = []; + if (complaintType === "ERS") { + // Search CM for any case files that may match based on authorization id + const { data, errors } = await get(token, { + query: `{getCasesFilesBySearchString (searchString: "${query}") + { + leadIdentifier, + caseIdentifier + } + }`, + }); + + if (errors) { + this.logger.error("GraphQL errors:", errors); + throw new Error("GraphQL errors occurred"); + } + + caseSearchData = data.getCasesFilesBySearchString; + } + builder.andWhere( - new Brackets(async (qb) => { + new Brackets((qb) => { qb.orWhere("complaint.complaint_identifier ILIKE :query", { query: `%${query}%`, }); @@ -502,23 +522,6 @@ export class ComplaintService { switch (complaintType) { case "ERS": { - // Search CM for any case files that may match based on authorization id - const { data, errors } = await get(token, { - query: `{getCasesFilesBySearchString (searchString: "${query}") - { - leadIdentifier, - caseIdentifier - } - }`, - }); - - if (errors) { - this.logger.error("GraphQL errors:", errors); - throw new Error("GraphQL errors occurred"); - } - - const caseSearchData = data.getCasesFilesBySearchString; - if (caseSearchData.length > 0) { qb.orWhere("complaint.complaint_identifier IN(:...complaint_identifiers)", { complaint_identifiers: caseSearchData.map((caseData) => caseData.leadIdentifier), @@ -1133,16 +1136,16 @@ export class ComplaintService { const includeCosOrganization: boolean = Boolean(query || filters.community || filters.zone || filters.region); let builder = this._generateMapQueryBuilder(complaintType, includeCosOrganization); - //-- apply search - if (query) { - builder = await this._applySearch(builder, complaintType, query, token); - } - //-- apply filters if used if (Object.keys(filters).length !== 0) { builder = this._applyFilters(builder, filters as ComplaintFilterParameters, complaintType); } + //-- apply search + if (query) { + builder = await this._applySearch(builder, complaintType, query, token); + } + //-- only return complaints for the agency the user is associated with const agency = hasCEEBRole ? "EPO" : (await this._getAgencyByUser()).agency_code; agency && builder.andWhere("complaint.owned_by_agency_code.agency_code = :agency", { agency }); diff --git a/backend/src/v1/officer/officer.service.ts b/backend/src/v1/officer/officer.service.ts index df0180621..b9ad26c4b 100644 --- a/backend/src/v1/officer/officer.service.ts +++ b/backend/src/v1/officer/officer.service.ts @@ -34,8 +34,8 @@ export class OfficerService { .leftJoinAndSelect("officer.office_guid", "office") .leftJoinAndSelect("officer.person_guid", "person") .leftJoinAndSelect("office.agency_code", "agency") - // This view is slow, no need to join and select for the properties that are mapped in this call - //.leftJoinAndSelect("office.cos_geo_org_unit", "cos_geo_org_unit") + // This view is slow :( + .leftJoinAndSelect("office.cos_geo_org_unit", "cos_geo_org_unit") .leftJoinAndSelect("office.agency_code", "agency_code") .orderBy("person.last_name", "ASC") .getMany(); diff --git a/frontend/src/app/components/containers/complaints/complaint-filter.tsx b/frontend/src/app/components/containers/complaints/complaint-filter.tsx index 722fe4f15..82de291e2 100644 --- a/frontend/src/app/components/containers/complaints/complaint-filter.tsx +++ b/frontend/src/app/components/containers/complaints/complaint-filter.tsx @@ -77,6 +77,7 @@ export const ComplaintFilter: FC = ({ type }) => { const setFilter = useCallback( (name: string, value?: Option | Date | null) => { let payload: ComplaintFilterPayload = { filter: name, value }; + dispatch(updateFilter(payload)); }, [dispatch], diff --git a/frontend/src/app/components/containers/complaints/complaint-map-with-server-side-clustering.tsx b/frontend/src/app/components/containers/complaints/complaint-map-with-server-side-clustering.tsx index 4e4a65de4..aa43457d6 100644 --- a/frontend/src/app/components/containers/complaints/complaint-map-with-server-side-clustering.tsx +++ b/frontend/src/app/components/containers/complaints/complaint-map-with-server-side-clustering.tsx @@ -7,7 +7,7 @@ import { ComplaintRequestPayload } from "@/app/types/complaints/complaint-filter import LeafletMapWithServerSideClustering from "@components/mapping/leaflet-map-with-server-side-clustering"; import { generateApiParameters, get } from "@common/api"; import config from "@/config"; -import { setMappedComplaintsCount } from "@/app/store/reducers/complaints"; +import { setComplaint, setComplaintSearchParameters, setMappedComplaintsCount } from "@/app/store/reducers/complaints"; type Props = { type: string; @@ -17,8 +17,11 @@ type Props = { export const generateMapComplaintRequestPayload = ( complaintType: string, filters: ComplaintFilters, + searchQuery: string, ): ComplaintRequestPayload => { const { + sortColumn, + sortOrder, region, zone, community, @@ -37,8 +40,8 @@ export const generateMapComplaintRequestPayload = ( } = filters; let common = { - sortColumn: "", // sort or order has no bearing on map data - sortOrder: "", // sort or order has no bearing on map data + sortColumn: sortColumn, + sortOrder: sortOrder, regionCodeFilter: region, zoneCodeFilter: zone, areaCodeFilter: community, @@ -49,6 +52,7 @@ export const generateMapComplaintRequestPayload = ( actionTakenFilter: actionTaken, outcomeAnimalStartDateFilter: outcomeAnimalStartDate, outcomeAnimalEndDateFilter: outcomeAnimalEndDate, + query: searchQuery, }; switch (complaintType) { @@ -96,7 +100,9 @@ export const ComplaintMapWithServerSideClustering: FC = ({ type, searchQu }, ) => { setLoadingMapData(true); - let payload = generateMapComplaintRequestPayload(type, filters); + let payload = generateMapComplaintRequestPayload(type, filters, searchQuery); + dispatch(setComplaint(null)); + dispatch(setComplaintSearchParameters(payload)); let parms: any = { bbox: bbox ? `${bbox.west},${bbox.south},${bbox.east},${bbox.north}` : undefined, // If the bbox is not provided, return all complaint clusters diff --git a/frontend/src/app/components/mapping/complaint-summary-popup.tsx b/frontend/src/app/components/mapping/complaint-summary-popup.tsx index a36212939..db68f9558 100644 --- a/frontend/src/app/components/mapping/complaint-summary-popup.tsx +++ b/frontend/src/app/components/mapping/complaint-summary-popup.tsx @@ -5,6 +5,7 @@ import { ComplaintDetails } from "@apptypes/complaints/details/complaint-details import { applyStatusClass, formatDate } from "@common/methods"; import { Badge, Button } from "react-bootstrap"; import { Popup } from "react-leaflet"; +import { useNavigate } from "react-router-dom"; interface Props { complaint_identifier: string; @@ -12,6 +13,7 @@ interface Props { } export const ComplaintSummaryPopup: FC = ({ complaint_identifier, complaintType }) => { + const navigate = useNavigate(); const { officerAssigned, natureOfComplaint, species, violationType, loggedDate, status, girType } = useAppSelector( selectComplaintHeader(complaintType), ); @@ -77,7 +79,7 @@ export const ComplaintSummaryPopup: FC = ({ complaint_identifier, complai size="sm" className="comp-map-popup-details-btn" id="view-complaint-details-button-id" - href={`/complaint/${complaintType}/${complaint_identifier}`} + onClick={() => navigate(`/complaint/${complaintType}/${complaint_identifier}`)} > View Details From 4276c20f63b39adbfb388f410e5d4b8c00ac4449 Mon Sep 17 00:00:00 2001 From: Jonathan Funk Date: Fri, 13 Dec 2024 12:05:43 -0800 Subject: [PATCH 03/28] Initial crunchy setup on PRs --- .github/workflows/pr-open.yml | 16 +- charts/crunchy/Chart.yaml | 26 ++ charts/crunchy/templates/PostgresCluster.yaml | 258 ++++++++++++++++++ charts/crunchy/templates/_helpers.tpl | 70 +++++ charts/crunchy/templates/knp.yaml | 17 ++ charts/crunchy/templates/secret.yaml | 11 + charts/crunchy/values.yaml | 147 ++++++++++ 7 files changed, 543 insertions(+), 2 deletions(-) create mode 100644 charts/crunchy/Chart.yaml create mode 100644 charts/crunchy/templates/PostgresCluster.yaml create mode 100644 charts/crunchy/templates/_helpers.tpl create mode 100644 charts/crunchy/templates/knp.yaml create mode 100644 charts/crunchy/templates/secret.yaml create mode 100644 charts/crunchy/values.yaml diff --git a/.github/workflows/pr-open.yml b/.github/workflows/pr-open.yml index b773278b6..87d22ded1 100644 --- a/.github/workflows/pr-open.yml +++ b/.github/workflows/pr-open.yml @@ -32,16 +32,28 @@ jobs: tag_fallback: latest triggers: ('${{ matrix.package }}/') + # https://github.com/bcgov/quickstart-openshift + # TODO: using latest for now, but move to tagged version + crunchy: + name: Deploy Crunchy + needs: [builds] + uses: bcgov/quickstart-openshift/.github/workflows/.deployer-db.yml + secrets: + oc_namespace: ${{ secrets.OC_NAMESPACE }} + oc_token: ${{ secrets.OC_TOKEN }} + with: + triggers: ('backend/' 'frontend/' 'webeoc/' 'migrations/' 'charts/') + # https://github.com/bcgov/quickstart-openshift-helpers deploys: name: Deploys - needs: [builds] + needs: [builds, crunchy] uses: bcgov/quickstart-openshift-helpers/.github/workflows/.deployer.yml@v0.5.0 secrets: oc_namespace: ${{ secrets.OC_NAMESPACE }} oc_token: ${{ secrets.OC_TOKEN }} with: - triggers: ('backend/' 'frontend/' 'webeoc/' 'migrations/') + triggers: ('backend/' 'frontend/' 'webeoc/' 'migrations/' 'charts/') healthcheck: name: Healthcheck Deployment diff --git a/charts/crunchy/Chart.yaml b/charts/crunchy/Chart.yaml new file mode 100644 index 000000000..223f5dfef --- /dev/null +++ b/charts/crunchy/Chart.yaml @@ -0,0 +1,26 @@ +apiVersion: v2 +name: crunchy +description: A Helm chart for Kubernetes deployment. +icon: https://www.nicepng.com/png/detail/521-5211827_bc-icon-british-columbia-government-logo.png + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 5.5.1 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "5.5.1" + diff --git a/charts/crunchy/templates/PostgresCluster.yaml b/charts/crunchy/templates/PostgresCluster.yaml new file mode 100644 index 000000000..2fc074f27 --- /dev/null +++ b/charts/crunchy/templates/PostgresCluster.yaml @@ -0,0 +1,258 @@ +{{- if .Values.crunchy.enabled}} +apiVersion: postgres-operator.crunchydata.com/v1beta1 +kind: PostgresCluster +metadata: + name: {{ template "crunchy-postgres.fullname" . }} + labels: {{ include "crunchy-postgres.labels" . | nindent 4 }} +spec: + metadata: + labels: {{ include "crunchy-postgres.labels" . | nindent 6 }} + {{ if .Values.crunchy.crunchyImage }} + image: {{ .Values.crunchy.crunchyImage }} + {{ end }} + imagePullPolicy: {{.Values.crunchy.imagePullPolicy}} + postgresVersion: {{ .Values.crunchy.postgresVersion }} + {{ if .Values.crunchy.postGISVersion }} + postGISVersion: {{ .Values.crunchy.postGISVersion | quote }} + {{ end }} + postgresVersion: {{ .Values.crunchy.postgresVersion }} + {{- if and .Values.crunchy.clone .Values.crunchy.clone.enabled }} # enabled in disaster recovery scenario + dataSource: + {{- if .Values.crunchy.clone.s3.enabled}} + pgbackrest: + stanza: {{ .Values.crunchy.instances.name }} + configuration: + - secret: + name: {{ .Release.Name }}-s3-secret + global: + repo2-s3-uri-style: path # This is mandatory since the backups are path based. + repo2-path: {{ .Values.crunchy.clone.path }} # path to the backup where cluster will bootstrap from + repo: + name: repo2 # hardcoded since repo 2, it is always backed up to object storage. + s3: + bucket: {{ .Values.crunchy.pgBackRest.s3.bucket }} + endpoint: {{ .Values.crunchy.pgBackRest.s3.endpoint }} + region: "ca-central-1" + {{- end}} + {{- if .Values.crunchy.clone.pvc.enabled}} + postgresCluster: + clusterName: {{ template "crunchy-postgres.fullname" . }} + repoName: repo1 + {{- end}} + {{- end}} + {{- if .Values.crunchy.pgmonitor.enabled }} + monitoring: + pgmonitor: + # this stuff is for the "exporter" container in the "postgres-cluster-ha" set of pods + exporter: + {{ if .Values.crunchy.pgmonitor.exporter.image}} + image: {{ .Values.crunchy.pgmonitor.exporter.image}} + {{ end }} + resources: + requests: + cpu: {{ .Values.crunchy.pgmonitor.exporter.requests.cpu }} + memory: {{ .Values.crunchy.pgmonitor.exporter.requests.memory }} + limits: + cpu: {{ .Values.crunchy.pgmonitor.exporter.limits.cpu }} + memory: {{ .Values.crunchy.pgmonitor.exporter.limits.memory }} + + {{ end }} + + instances: + - name: {{ .Values.crunchy.instances.name }} + {{- if .Values.crunchy.instances.metadata }} + metadata: + {{- toYaml .Values.crunchy.instances.metadata | nindent 8 }} + {{- end }} + replicas: {{ .Values.crunchy.instances.replicas }} + resources: + requests: + cpu: {{ .Values.crunchy.instances.requests.cpu }} + memory: {{ .Values.crunchy.instances.requests.memory }} + limits: + cpu: {{ .Values.crunchy.instances.limits.cpu }} + memory: {{ .Values.crunchy.instances.limits.memory }} + + sidecars: + replicaCertCopy: + resources: + requests: + cpu: {{ .Values.crunchy.instances.replicaCertCopy.requests.cpu }} + memory: {{ .Values.crunchy.instances.replicaCertCopy.requests.memory }} + limits: + cpu: {{ .Values.crunchy.instances.replicaCertCopy.limits.cpu }} + memory: {{ .Values.crunchy.instances.replicaCertCopy.limits.memory }} + dataVolumeClaimSpec: + accessModes: + - "ReadWriteOnce" + resources: + requests: + storage: {{ .Values.crunchy.instances.dataVolumeClaimSpec.storage }} + storageClassName: {{ .Values.crunchy.instances.dataVolumeClaimSpec.storageClassName }} + walVolumeClaimSpec: + accessModes: + - "ReadWriteOnce" + resources: + requests: + storage: {{ .Values.crunchy.instances.dataVolumeClaimSpec.walStorage }} + storageClassName: {{ .Values.crunchy.instances.dataVolumeClaimSpec.storageClassName }} + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 1 + podAffinityTerm: + topologyKey: kubernetes.io/hostname + labelSelector: + matchLabels: + postgres-operator.crunchydata.com/cluster: + {{ template "crunchy-postgres.fullname" . }} + postgres-operator.crunchydata.com/instance-set: {{ .Values.crunchy.instances.name }} + + users: + - name: {{ .Values.global.config.dbName }} + databases: + - {{ .Values.global.config.dbName }} + options: "SUPERUSER CREATEDB CREATEROLE" + - name: postgres + databases: + - postgres + - {{ .Values.global.config.dbName }} + - name: {{ .Values.global.config.dbName }}proxy # this user lets dev connect to postgres via pgbouncer from local system + databases: + - {{ .Values.global.config.dbName }} + - postgres + {{- if .Values.crunchy.pgBackRest.enabled }} + backups: + pgbackrest: + {{ if .Values.crunchy.pgBackRest.image }} + image: {{ .Values.crunchy.pgBackRest.image }} + {{ end }} + {{- if .Values.crunchy.pgBackRest.s3.enabled}} + configuration: + - secret: + name: {{ .Release.Name }}-s3-secret + {{- end }} + global: + repo1-retention-full: {{ .Values.crunchy.pgBackRest.pvc.retention | quote }} + repo1-retention-full-type: {{ .Values.crunchy.pgBackRest.pvc.retentionFullType }} + {{- if .Values.crunchy.pgBackRest.s3.enabled}} + repo2-retention-full: {{ .Values.crunchy.pgBackRest.s3.retention | quote }} + repo2-retention-full-type: {{ .Values.crunchy.pgBackRest.retentionFullType }} + repo2-path: '{{ .Values.crunchy.pgBackRest.backupPath }}/{{ .Values.crunchy.pgBackRest.clusterCounter}}' + repo2-s3-uri-style: path + {{- end }} + repos: + + - name: repo1 + schedules: + full: {{ .Values.crunchy.pgBackRest.pvc.fullBackupSchedule }} + incremental: {{ .Values.crunchy.pgBackRest.pvc.incrementalBackupSchedule }} + volume: + volumeClaimSpec: + accessModes: + - {{ .Values.crunchy.pgBackRest.pvc.volume.accessModes }} + resources: + requests: + storage: {{ .Values.crunchy.pgBackRest.pvc.volume.storage }} + storageClassName: {{ .Values.crunchy.pgBackRest.pvc.volume.storageClassName }} + {{- if .Values.crunchy.pgBackRest.s3.enabled}} + - name: repo2 + schedules: + full: {{ .Values.crunchy.pgBackRest.s3.fullBackupSchedule }} + incremental: {{ .Values.crunchy.pgBackRest.s3.incrementalBackupSchedule }} + s3: + bucket: {{ .Values.crunchy.pgBackRest.s3.bucket | quote }} + endpoint: {{ .Values.crunchy.pgBackRest.s3.endpoint | quote }} + region: "ca-central-1" + {{- end }} + {{- if and .Values.crunchy.restore .Values.crunchy.restore.enabled }} + restore: + enabled: {{ .Values.crunchy.restore.enabled }} + repoName: {{ .Values.crunchy.restore.repoName }} + options: + - --type=time + - --target="{{ .Values.crunchy.restore.target }}" + {{- end }} + # this stuff is for the "pgbackrest" container (the only non-init container) in the "postgres-crunchy-repo-host" pod + repoHost: + resources: + requests: + cpu: {{ .Values.crunchy.pgBackRest.repoHost.requests.cpu }} + memory: {{ .Values.crunchy.pgBackRest.repoHost.requests.memory }} + limits: + cpu: {{ .Values.crunchy.pgBackRest.repoHost.limits.cpu }} + memory: {{ .Values.crunchy.pgBackRest.repoHost.limits.memory }} + sidecars: + # this stuff is for the "pgbackrest" container in the "postgres-crunchy-ha" set of pods + pgbackrest: + resources: + requests: + cpu: {{ .Values.crunchy.pgBackRest.sidecars.requests.cpu }} + memory: {{ .Values.crunchy.pgBackRest.sidecars.requests.memory }} + limits: + cpu: {{ .Values.crunchy.pgBackRest.sidecars.limits.cpu }} + memory: {{ .Values.crunchy.pgBackRest.sidecars.limits.memory }} + pgbackrestConfig: + resources: + requests: + cpu: {{ .Values.crunchy.pgBackRest.sidecars.requests.cpu }} + memory: {{ .Values.crunchy.pgBackRest.sidecars.requests.memory }} + limits: + cpu: {{ .Values.crunchy.pgBackRest.sidecars.limits.cpu }} + memory: {{ .Values.crunchy.pgBackRest.sidecars.limits.memory }} + jobs: + resources: + requests: + cpu: {{ .Values.crunchy.pgBackRest.jobs.requests.cpu }} + memory: {{ .Values.crunchy.pgBackRest.jobs.requests.memory }} + limits: + cpu: {{ .Values.crunchy.pgBackRest.jobs.limits.cpu }} + memory: {{ .Values.crunchy.pgBackRest.jobs.limits.memory }} + {{- end }} + patroni: + dynamicConfiguration: + postgresql: + pg_hba: {{ toYaml .Values.crunchy.patroni.postgresql.pg_hba | nindent 10 }} + parameters: + log_min_duration_statement: {{ .Values.crunchy.patroni.postgresql.parameters.log_min_duration_statement }} + shared_buffers: {{ .Values.crunchy.patroni.postgresql.parameters.shared_buffers }} + wal_buffers: {{ .Values.crunchy.patroni.postgresql.parameters.wal_buffers }} + work_mem: {{ .Values.crunchy.patroni.postgresql.parameters.work_mem }} + min_wal_size: {{ .Values.crunchy.patroni.postgresql.parameters.min_wal_size }} + max_wal_size: {{ .Values.crunchy.patroni.postgresql.parameters.max_wal_size }} + max_slot_wal_keep_size: {{ .Values.crunchy.patroni.postgresql.parameters.max_slot_wal_keep_size }} + effective_io_concurrency: {{ .Values.crunchy.patroni.postgresql.parameters.effective_io_concurrency }} + {{- if and .Values.crunchy.proxy .Values.crunchy.proxy.enabled }} + proxy: + pgBouncer: + config: + global: + client_tls_sslmode: disable + pool_mode: session + max_db_connections: {{ .Values.crunchy.proxy.pgBouncer.maxConnections | quote }} + {{ if .Values.crunchy.proxy.pgBouncer.image }} + image: {{ .Values.crunchy.proxy.pgBouncer.image }} + {{ end }} + replicas: {{ .Values.crunchy.proxy.pgBouncer.replicas }} + # these resources are for the "pgbouncer" container in the "postgres-crunchy-ha-pgbouncer" set of pods + # there is a sidecar in these pods which are not mentioned here, but the requests/limits are teeny weeny by default so no worries there. + resources: + requests: + cpu: {{ .Values.crunchy.proxy.pgBouncer.requests.cpu }} + memory: {{ .Values.crunchy.proxy.pgBouncer.requests.memory }} + limits: + cpu: {{ .Values.crunchy.proxy.pgBouncer.limits.cpu }} + memory: {{ .Values.crunchy.proxy.pgBouncer.limits.memory }} + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 1 + podAffinityTerm: + topologyKey: kubernetes.io/hostname + labelSelector: + matchLabels: + postgres-operator.crunchydata.com/cluster: + {{ template "crunchy-postgres.fullname" . }} + postgres-operator.crunchydata.com/role: pgbouncer + {{- end }} + {{- end }} diff --git a/charts/crunchy/templates/_helpers.tpl b/charts/crunchy/templates/_helpers.tpl new file mode 100644 index 000000000..d6e89c6cc --- /dev/null +++ b/charts/crunchy/templates/_helpers.tpl @@ -0,0 +1,70 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "crunchy-postgres.name" -}} +{{- default "crunchy" .Values.crunchy.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "crunchy-postgres.fullname" -}} +{{- if .Values.crunchy.fullnameOverride }} +{{- .Values.crunchy.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default "crunchy" .Values.crunchy.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "crunchy-postgres.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "crunchy-postgres.labels" -}} +helm.sh/chart: {{ include "crunchy-postgres.chart" . }} +{{ include "crunchy-postgres.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "crunchy-postgres.selectorLabels" -}} +app.kubernetes.io/name: {{ include "crunchy-postgres.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "crunchy-postgres.serviceAccountName" -}} +{{- if .Values.crunchy.serviceAccount.create }} +{{- default (include "crunchy-postgres.fullname" .) .Values.crunchy.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.crunchy.serviceAccount.name }} +{{- end }} +{{- end }} + +{{- define "crunchy.s3" }} +{{- if .Values.crunchy.pgBackRest.s3.enabled}} +[global] +repo2-s3-key={{ .Values.crunchy.pgBackRest.s3.accessKey }} +repo2-s3-key-secret={{ .Values.crunchy.pgBackRest.s3.secretKey }} +{{ end }} +{{ end }} diff --git a/charts/crunchy/templates/knp.yaml b/charts/crunchy/templates/knp.yaml new file mode 100644 index 000000000..9624fdf74 --- /dev/null +++ b/charts/crunchy/templates/knp.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ .Release.Name }} + labels: {{- include "crunchy-postgres.selectorLabels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + postgres-operator.crunchydata.com/cluster: {{ template "crunchy-postgres.fullname" . }} + ingress: + - from: + - podSelector: + matchLabels: + postgres-operator.crunchydata.com/cluster: {{ template "crunchy-postgres.fullname" . }} + policyTypes: + - Ingress diff --git a/charts/crunchy/templates/secret.yaml b/charts/crunchy/templates/secret.yaml new file mode 100644 index 000000000..3d05f9eb0 --- /dev/null +++ b/charts/crunchy/templates/secret.yaml @@ -0,0 +1,11 @@ +{{- if and .Values.crunchy.enabled .Values.crunchy.pgBackRest.s3.enabled}} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{.Release.Name}}-s3-secret +type: Opaque +stringData: + s3.conf: |- + {{ include "crunchy.s3" . | nindent 8}} +{{- end }} diff --git a/charts/crunchy/values.yaml b/charts/crunchy/values.yaml new file mode 100644 index 000000000..d833f42d4 --- /dev/null +++ b/charts/crunchy/values.yaml @@ -0,0 +1,147 @@ +global: + config: + dbName: app #test +crunchy: # enable it for TEST and PROD, for PR based pipelines simply use single postgres + enabled: true + crunchyImage: artifacts.developer.gov.bc.ca/bcgov-docker-local/crunchy-postgres-gis:ubi8-16.2-3.4-0 + postgresVersion: 16 + postGISVersion: '3.4' + imagePullPolicy: IfNotPresent + # enable below to start a new crunchy cluster after disaster from a backed-up location, crunchy will choose the best place to recover from. + # follow https://access.crunchydata.com/documentation/postgres-operator/5.2.0/tutorial/disaster-recovery/ + # Clone From Backups Stored in S3 / GCS / Azure Blob Storage + clone: + enabled: false + s3: + enabled: false + pvc: + enabled: false + path: ~ # provide the proper path to source the cluster. ex: /backups/cluster/version/1, if current new cluster being created, this should be current cluster version -1, ideally + # enable this to go back to a specific timestamp in history in the current cluster. + # follow https://access.crunchydata.com/documentation/postgres-operator/5.2.0/tutorial/disaster-recovery/ + # Perform an In-Place Point-in-time-Recovery (PITR) + restore: + repoName: ~ # provide repo name + enabled: false + target: ~ # 2024-03-24 17:16:00-07 this is the target timestamp to go back to in current cluster + instances: + name: db # high availability + replicas: 2 # 2 or 3 for high availability in TEST and PROD. + metadata: + annotations: + prometheus.io/scrape: 'true' + prometheus.io/port: '9187' + dataVolumeClaimSpec: + storage: 200Mi + storageClassName: netapp-block-standard + walStorage: 255Mi + + requests: + cpu: 50m + memory: 128Mi + limits: + cpu: 150m + memory: 256Mi + replicaCertCopy: + requests: + cpu: 1m + memory: 32Mi + limits: + cpu: 50m + memory: 64Mi + + pgBackRest: + enabled: true + backupPath: /backups/test/cluster/version # change it for PROD, create values-prod.yaml + clusterCounter: 1 # this is the number to identify what is the current counter for the cluster, each time it is cloned it should be incremented. + image: artifacts.developer.gov.bc.ca/bcgov-docker-local/crunchy-pgbackrest:ubi8-2.49-0 + # If retention-full-type set to 'count' then the oldest backups will expire when the number of backups reach the number defined in retention + # If retention-full-type set to 'time' then the number defined in retention will take that many days worth of full backups before expiration + retentionFullType: count + s3: + enabled: false # if enabled, below must be provided + retention: 7 # one weeks backup in object store. + bucket: ~ + endpoint: ~ + accessKey: ~ + secretKey: ~ + fullBackupSchedule: ~ # make sure to provide values here, if s3 is enabled. + incrementalBackupSchedule: ~ # make sure to provide values here, if s3 is enabled. + pvc: + retention: 1 # one day hot active backup in pvc + retentionFullType: count + fullBackupSchedule: 0 8 * * * + incrementalBackupSchedule: 0 0,12 * * * # every 12 hour incremental + volume: + accessModes: "ReadWriteOnce" + storage: 100Mi + storageClassName: netapp-file-backup + + config: + requests: + cpu: 5m + memory: 32Mi + limits: + cpu: 20m + memory: 64Mi + repoHost: + requests: + cpu: 20m + memory: 128Mi + limits: + cpu: 50m + memory: 256Mi + sidecars: + requests: + cpu: 5m + memory: 16Mi + limits: + cpu: 20m + memory: 64Mi + jobs: + requests: + cpu: 20m + memory: 128Mi + limits: + cpu: 100m + memory: 256Mi + + patroni: + postgresql: + pg_hba: + - "host all all 0.0.0.0/0 md5" + - "host all all ::1/128 md5" + parameters: + shared_buffers: 16MB # default is 128MB; a good tuned default for shared_buffers is 25% of the memory allocated to the pod + wal_buffers: "64kB" # this can be set to -1 to automatically set as 1/32 of shared_buffers or 64kB, whichever is larger + min_wal_size: 32MB + max_wal_size: 64MB # default is 1GB + max_slot_wal_keep_size: 128MB # default is -1, allowing unlimited wal growth when replicas fall behind + work_mem: 2MB # a work_mem value of 2 MB + log_min_duration_statement: 1000ms # log queries taking more than 1 second to respond. + effective_io_concurrency: 20 #If the underlying disk can handle multiple simultaneous requests, then you should increase the effective_io_concurrency value and test what value provides the best application performance. All BCGov clusters have SSD. + + proxy: + enabled: true + pgBouncer: + image: # it's not necessary to specify an image as the images specified in the Crunchy Postgres Operator will be pulled by default + replicas: 1 + requests: + cpu: 5m + memory: 32Mi + limits: + cpu: 20m + memory: 64Mi + maxConnections: 10 # make sure less than postgres max connections + + # Postgres Cluster resource values: + pgmonitor: + enabled: false + exporter: + image: # it's not necessary to specify an image as the images specified in the Crunchy Postgres Operator will be pulled by default + requests: + cpu: 1m + memory: 16Mi + limits: + cpu: 35m + memory: 32Mi From 8b8067686d0de2d2238d4a786e07e0b976667a26 Mon Sep 17 00:00:00 2001 From: Jonathan Funk Date: Fri, 13 Dec 2024 12:15:49 -0800 Subject: [PATCH 04/28] remove triggers --- .github/workflows/pr-open.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/pr-open.yml b/.github/workflows/pr-open.yml index 87d22ded1..c3d4bd731 100644 --- a/.github/workflows/pr-open.yml +++ b/.github/workflows/pr-open.yml @@ -41,8 +41,6 @@ jobs: secrets: oc_namespace: ${{ secrets.OC_NAMESPACE }} oc_token: ${{ secrets.OC_TOKEN }} - with: - triggers: ('backend/' 'frontend/' 'webeoc/' 'migrations/' 'charts/') # https://github.com/bcgov/quickstart-openshift-helpers deploys: @@ -52,8 +50,6 @@ jobs: secrets: oc_namespace: ${{ secrets.OC_NAMESPACE }} oc_token: ${{ secrets.OC_TOKEN }} - with: - triggers: ('backend/' 'frontend/' 'webeoc/' 'migrations/' 'charts/') healthcheck: name: Healthcheck Deployment From 3f9de170a346d06545ec643015830ddea561a081 Mon Sep 17 00:00:00 2001 From: Jonathan Funk Date: Mon, 16 Dec 2024 10:26:19 -0800 Subject: [PATCH 05/28] temp: always deplyo crunchy --- .github/workflows/pr-open.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-open.yml b/.github/workflows/pr-open.yml index c3d4bd731..21a6ab397 100644 --- a/.github/workflows/pr-open.yml +++ b/.github/workflows/pr-open.yml @@ -36,7 +36,7 @@ jobs: # TODO: using latest for now, but move to tagged version crunchy: name: Deploy Crunchy - needs: [builds] + # needs: [builds] uses: bcgov/quickstart-openshift/.github/workflows/.deployer-db.yml secrets: oc_namespace: ${{ secrets.OC_NAMESPACE }} From 7ebdbabb6aad1eb3529bc70ff59297642c2feb13 Mon Sep 17 00:00:00 2001 From: Jonathan Funk Date: Mon, 16 Dec 2024 13:21:45 -0800 Subject: [PATCH 06/28] specify crunchy deploy sha --- .github/workflows/pr-open.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/pr-open.yml b/.github/workflows/pr-open.yml index 21a6ab397..1318132e3 100644 --- a/.github/workflows/pr-open.yml +++ b/.github/workflows/pr-open.yml @@ -33,11 +33,10 @@ jobs: triggers: ('${{ matrix.package }}/') # https://github.com/bcgov/quickstart-openshift - # TODO: using latest for now, but move to tagged version crunchy: name: Deploy Crunchy # needs: [builds] - uses: bcgov/quickstart-openshift/.github/workflows/.deployer-db.yml + uses: bcgov/quickstart-openshift/.github/workflows/.deployer-db.yml@8b9c1c1c1b3417007fb746595680a5df12ac1204 secrets: oc_namespace: ${{ secrets.OC_NAMESPACE }} oc_token: ${{ secrets.OC_TOKEN }} From b3f146da85ab9e38c6e4d0857fdb5be74c96a088 Mon Sep 17 00:00:00 2001 From: afwilcox Date: Mon, 16 Dec 2024 14:58:27 -0800 Subject: [PATCH 07/28] fix: fix bad merge --- .../complaints/complaint-map-with-server-side-clustering.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/src/app/components/containers/complaints/complaint-map-with-server-side-clustering.tsx b/frontend/src/app/components/containers/complaints/complaint-map-with-server-side-clustering.tsx index 37bd21d34..194a2a213 100644 --- a/frontend/src/app/components/containers/complaints/complaint-map-with-server-side-clustering.tsx +++ b/frontend/src/app/components/containers/complaints/complaint-map-with-server-side-clustering.tsx @@ -30,8 +30,6 @@ export const generateMapComplaintRequestPayload = ( searchQuery: string, ): ComplaintRequestPayload => { const { - sortColumn, - sortOrder, region, zone, community, From f03f7fb19dbacb47143a497f4d8646d64d3b4410 Mon Sep 17 00:00:00 2001 From: Ryan Rondeau Date: Mon, 16 Dec 2024 17:37:58 -0800 Subject: [PATCH 08/28] chore: CE-1274 (#827) --- .../app/common/validation-checkbox-group.tsx | 2 +- .../src/app/common/validation-phone-input.tsx | 2 +- .../common/attachments-carousel.tsx | 6 +- .../common/comp-coordinate-input.tsx | 256 ++++++++++-------- .../common/complaint-pagination.tsx | 2 +- .../containers/admin/feature-management.tsx | 17 +- .../allegation-complaint-list-item.tsx | 17 -- .../wildlife-complaint-list-item.tsx | 17 -- .../ceeb/ceeb-decision/decision-item.tsx | 2 - .../outcomes/hwcr-complaint-assessment.tsx | 2 +- .../hwcr-equipment/equipment-form.tsx | 28 +- .../outcomes/hwcr-outcome-by-animal-v2.tsx | 2 + .../oucome-by-animal/animal-outcome.tsx | 1 - .../oucome-by-animal/drug-authorized-by.tsx | 1 - .../outcomes/oucome-by-animal/drug-item.tsx | 2 - .../outcomes/outcome-attachments.tsx | 2 +- .../office-user-container.tsx | 1 - .../modal/instances/assign-officer-modal.tsx | 2 + .../modal/instances/cancel-confirm-modal.tsx | 2 +- .../instances/delete-confirm-modal-v2.tsx | 2 +- .../standalone-cancel-confirm-modal.tsx | 2 +- frontend/src/app/store/reducers/app.ts | 2 +- 22 files changed, 177 insertions(+), 193 deletions(-) diff --git a/frontend/src/app/common/validation-checkbox-group.tsx b/frontend/src/app/common/validation-checkbox-group.tsx index d2789f0ae..e36277ead 100644 --- a/frontend/src/app/common/validation-checkbox-group.tsx +++ b/frontend/src/app/common/validation-checkbox-group.tsx @@ -34,7 +34,7 @@ export const ValidationCheckboxGroup: FC = ({ useEffect(() => { setCheckedItems(checkedValues); - }, [checkedValues.length]); + }, [checkedValues, checkedValues.length]); return (
diff --git a/frontend/src/app/common/validation-phone-input.tsx b/frontend/src/app/common/validation-phone-input.tsx index ca0b961d2..f8c4b1d47 100644 --- a/frontend/src/app/common/validation-phone-input.tsx +++ b/frontend/src/app/common/validation-phone-input.tsx @@ -21,7 +21,7 @@ export const ValidationPhoneInput: FC = ({ international, }) => { const errClass = errMsg === "" ? "" : "error-message"; - const calulatedClass = errMsg === "" ? "comp-form-control" : "comp-form-control" + " error-border"; + const calulatedClass = errMsg === "" ? "comp-form-control" : "comp-form-control error-border"; return (
diff --git a/frontend/src/app/components/common/attachments-carousel.tsx b/frontend/src/app/components/common/attachments-carousel.tsx index 69a48b1bf..9fe567585 100644 --- a/frontend/src/app/components/common/attachments-carousel.tsx +++ b/frontend/src/app/components/common/attachments-carousel.tsx @@ -64,7 +64,7 @@ export const AttachmentsCarousel: FC = ({ if (complaintIdentifier) { dispatch(getAttachments(complaintIdentifier, attachmentType)); } - }, [complaintIdentifier, dispatch]); + }, [attachmentType, complaintIdentifier, dispatch]); //-- when the component unmounts clear the attachments from redux useEffect(() => { @@ -82,7 +82,7 @@ export const AttachmentsCarousel: FC = ({ if (typeof onSlideCountChange === "function") { onSlideCountChange(slides.length); } - }, [slides.length]); + }, [onSlideCountChange, slides.length]); // Clear all pending upload attachments useEffect(() => { @@ -90,7 +90,7 @@ export const AttachmentsCarousel: FC = ({ setSlides([]); if (setCancelPendingUpload) setCancelPendingUpload(false); //reset cancelPendingUpload } - }, [cancelPendingUpload]); + }, [cancelPendingUpload, setCancelPendingUpload]); function sortAttachmentsByName(comsObjects: COMSObject[]): COMSObject[] { // Create a copy of the array using slice() or spread syntax diff --git a/frontend/src/app/components/common/comp-coordinate-input.tsx b/frontend/src/app/components/common/comp-coordinate-input.tsx index 6f0ad1769..362dc160a 100644 --- a/frontend/src/app/components/common/comp-coordinate-input.tsx +++ b/frontend/src/app/components/common/comp-coordinate-input.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useState } from "react"; +import { FC, useCallback, useEffect, useState } from "react"; import Option from "@apptypes/app/option"; import { CompRadioGroup } from "./comp-radiogroup"; import { bcBoundaries, bcUtmBoundaries, formatLatLongCoordinate } from "@common/methods"; @@ -46,141 +46,158 @@ export const CompCoordinateInput: FC = ({ const [zoneCoordinate, setZoneCoordinate] = useState
+
{xCoordinateErrorMsg || yCoordinateErrorMsg}
{ diff --git a/frontend/src/app/components/containers/complaints/outcomes/hwcr-outcome-by-animal-v2.tsx b/frontend/src/app/components/containers/complaints/outcomes/hwcr-outcome-by-animal-v2.tsx index c7230cd9f..42d545ddf 100644 --- a/frontend/src/app/components/containers/complaints/outcomes/hwcr-outcome-by-animal-v2.tsx +++ b/frontend/src/app/components/containers/complaints/outcomes/hwcr-outcome-by-animal-v2.tsx @@ -149,6 +149,8 @@ export const HWCROutcomeByAnimalv2: FC = () => { } } } + // officersInAgencyList should be a dependency but its selector needs to be refactored using a selector creator to avoid an infinte loop here by adding it + // eslint-disable-next-line react-hooks/exhaustive-deps }, [complaint]); useEffect(() => { diff --git a/frontend/src/app/components/containers/complaints/outcomes/oucome-by-animal/animal-outcome.tsx b/frontend/src/app/components/containers/complaints/outcomes/oucome-by-animal/animal-outcome.tsx index 48e2a7415..f4c7c2196 100644 --- a/frontend/src/app/components/containers/complaints/outcomes/oucome-by-animal/animal-outcome.tsx +++ b/frontend/src/app/components/containers/complaints/outcomes/oucome-by-animal/animal-outcome.tsx @@ -3,7 +3,6 @@ import type { AnimalOutcome as AnimalOutcomeData } from "@apptypes/app/complaint import { useAppSelector } from "@hooks/hooks"; import { selectAgeDropdown, - selectConflictHistoryDropdown, selectEarDropdown, selectSexDropdown, selectSpeciesCodeDropdown, diff --git a/frontend/src/app/components/containers/complaints/outcomes/oucome-by-animal/drug-authorized-by.tsx b/frontend/src/app/components/containers/complaints/outcomes/oucome-by-animal/drug-authorized-by.tsx index 404c48c0a..56cfc11b4 100644 --- a/frontend/src/app/components/containers/complaints/outcomes/oucome-by-animal/drug-authorized-by.tsx +++ b/frontend/src/app/components/containers/complaints/outcomes/oucome-by-animal/drug-authorized-by.tsx @@ -20,7 +20,6 @@ type refProps = { export const DrugAuthorizedBy = forwardRef((props, ref) => { const { - agency, update, drugAuthorization: { officer, date }, } = props; diff --git a/frontend/src/app/components/containers/complaints/outcomes/oucome-by-animal/drug-item.tsx b/frontend/src/app/components/containers/complaints/outcomes/oucome-by-animal/drug-item.tsx index 67eb9ee16..caf99b651 100644 --- a/frontend/src/app/components/containers/complaints/outcomes/oucome-by-animal/drug-item.tsx +++ b/frontend/src/app/components/containers/complaints/outcomes/oucome-by-animal/drug-item.tsx @@ -5,7 +5,6 @@ import { selectDrugs, selectDrugUseMethods, selectRemainingDrugUse } from "@stor import { formatDate } from "@common/methods"; import { selectOfficerListByAgency } from "@store/reducers/officer"; import { from } from "linq-to-typescript"; -import { selectComplaint } from "@store/reducers/complaints"; type props = { vial: string; @@ -31,7 +30,6 @@ export const DrugItem: FC = ({ officer, date, }) => { - const complaintData = useAppSelector(selectComplaint); const drugs = useAppSelector(selectDrugs); const drugUseMethods = useAppSelector(selectDrugUseMethods); const remainingDrugUse = useAppSelector(selectRemainingDrugUse); diff --git a/frontend/src/app/components/containers/complaints/outcomes/outcome-attachments.tsx b/frontend/src/app/components/containers/complaints/outcomes/outcome-attachments.tsx index 07510ff8e..41a066c06 100644 --- a/frontend/src/app/components/containers/complaints/outcomes/outcome-attachments.tsx +++ b/frontend/src/app/components/containers/complaints/outcomes/outcome-attachments.tsx @@ -73,7 +73,7 @@ export const OutcomeAttachments: FC = ({ showAddButton = false }) => { } else { setComponentState(EDIT_STATE); } - }, [carouselData]); + }, [carouselData, showAddButton]); const handleSlideCountChange = (count: number) => { setOutcomeAttachmentCount(count); diff --git a/frontend/src/app/components/containers/zone-at-a-glance/office-user-container.tsx b/frontend/src/app/components/containers/zone-at-a-glance/office-user-container.tsx index b742bffb5..b22a63340 100644 --- a/frontend/src/app/components/containers/zone-at-a-glance/office-user-container.tsx +++ b/frontend/src/app/components/containers/zone-at-a-glance/office-user-container.tsx @@ -1,7 +1,6 @@ import { FC } from "react"; import { OfficerStats } from "@apptypes/complaints/zone-at-a-glance-stats"; import { Row, Col } from "react-bootstrap"; -import config from "@/config"; import { isFeatureActive } from "@store/reducers/app"; import { useAppSelector } from "@hooks/hooks"; import { FEATURE_TYPES } from "@constants/feature-flag-types"; diff --git a/frontend/src/app/components/modal/instances/assign-officer-modal.tsx b/frontend/src/app/components/modal/instances/assign-officer-modal.tsx index cca81c091..6710e89b5 100644 --- a/frontend/src/app/components/modal/instances/assign-officer-modal.tsx +++ b/frontend/src/app/components/modal/instances/assign-officer-modal.tsx @@ -137,6 +137,8 @@ export const AssignOfficerModal: FC = ({ close, submit,
); + } else { + return <>; } }); } diff --git a/frontend/src/app/components/modal/instances/cancel-confirm-modal.tsx b/frontend/src/app/components/modal/instances/cancel-confirm-modal.tsx index 0fbcff7c0..cbefae7ba 100644 --- a/frontend/src/app/components/modal/instances/cancel-confirm-modal.tsx +++ b/frontend/src/app/components/modal/instances/cancel-confirm-modal.tsx @@ -1,5 +1,5 @@ import { FC } from "react"; -import { Modal, Row, Col, Button } from "react-bootstrap"; +import { Modal, Button } from "react-bootstrap"; import { useAppSelector } from "@hooks/hooks"; import { selectModalData } from "@store/reducers/app"; diff --git a/frontend/src/app/components/modal/instances/delete-confirm-modal-v2.tsx b/frontend/src/app/components/modal/instances/delete-confirm-modal-v2.tsx index 57477162a..b059e592a 100644 --- a/frontend/src/app/components/modal/instances/delete-confirm-modal-v2.tsx +++ b/frontend/src/app/components/modal/instances/delete-confirm-modal-v2.tsx @@ -1,5 +1,5 @@ import { FC } from "react"; -import { Modal, Row, Col, Button } from "react-bootstrap"; +import { Modal, Button } from "react-bootstrap"; import { useAppSelector } from "@hooks/hooks"; import { selectModalData } from "@store/reducers/app"; diff --git a/frontend/src/app/components/modal/instances/standalone-cancel-confirm-modal.tsx b/frontend/src/app/components/modal/instances/standalone-cancel-confirm-modal.tsx index 59d778259..056a10676 100644 --- a/frontend/src/app/components/modal/instances/standalone-cancel-confirm-modal.tsx +++ b/frontend/src/app/components/modal/instances/standalone-cancel-confirm-modal.tsx @@ -4,7 +4,7 @@ //-- import { FC, useEffect, useState } from "react"; -import { Button, Col, Modal, Row } from "react-bootstrap"; +import { Button, Modal } from "react-bootstrap"; type props = { show: boolean; diff --git a/frontend/src/app/store/reducers/app.ts b/frontend/src/app/store/reducers/app.ts index 6cdfc9e20..a1a851a30 100644 --- a/frontend/src/app/store/reducers/app.ts +++ b/frontend/src/app/store/reducers/app.ts @@ -518,7 +518,7 @@ export const validateComsAccess = async (dispatch) => { try { const parameters = generateApiParameters(`${config.COMS_URL}/permission/invite/${token}`); - const response = await get(dispatch, parameters); + await get(dispatch, parameters); return { status: "success" }; } catch (error) { const { response } = error as AxiosError; From 71ea76992115030226dcf3a060df3e2c3fbc11fb Mon Sep 17 00:00:00 2001 From: Scarlett <35635257+Scarlett-Truong@users.noreply.github.com> Date: Tue, 17 Dec 2024 08:52:26 -0800 Subject: [PATCH 09/28] fix: CE-1291 zone at a glance-enforcement not correct (#819) Co-authored-by: afwilcox --- .../components/containers/zone-at-a-glance/zone-at-a-glance.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/app/components/containers/zone-at-a-glance/zone-at-a-glance.tsx b/frontend/src/app/components/containers/zone-at-a-glance/zone-at-a-glance.tsx index 4f1b562f5..203a26449 100644 --- a/frontend/src/app/components/containers/zone-at-a-glance/zone-at-a-glance.tsx +++ b/frontend/src/app/components/containers/zone-at-a-glance/zone-at-a-glance.tsx @@ -22,7 +22,6 @@ export const ZoneAtAGlance: FC = () => { if (currentZone) { dispatch(getZoneAtAGlanceStats(currentZone, ComplaintType.HWCR_COMPLAINT)); dispatch(getZoneAtAGlanceStats(currentZone, ComplaintType.ALLEGATION_COMPLAINT)); - dispatch(getZoneAtAGlanceStats(currentZone, ComplaintType.GENERAL_COMPLAINT)); } }, [dispatch, currentZone]); From cf778949a6b5086c6cd344ed8b80ce3a1f6f90d5 Mon Sep 17 00:00:00 2001 From: dmitri-korin-bcps <108112696+dmitri-korin-bcps@users.noreply.github.com> Date: Tue, 17 Dec 2024 09:52:46 -0800 Subject: [PATCH 10/28] feat: CE-1082-Add-attachment-summary-to-PDF-exports (#828) Co-authored-by: afwilcox --- backend/src/common/methods.ts | 34 ++++++++++++++ .../complaints/export-complaint-parameters.ts | 8 ++++ .../src/types/models/general/attachment.ts | 13 ++++++ backend/src/v1/complaint/complaint.service.ts | 34 +++++++++++++- .../src/v1/document/document.controller.ts | 44 +++++++++++++----- backend/src/v1/document/document.service.ts | 12 ++++- .../CDOGS-CEEB-COMPLAINT-TEMPLATE-v1.docx | Bin 303114 -> 303840 bytes .../CDOGS-ERS-COMPLAINT-TEMPLATE-v1.docx | Bin 318439 -> 320081 bytes .../CDOGS-HWCR-COMPLAINT-TEMPLATE-v1.docx | Bin 336866 -> 339142 bytes .../app/store/reducers/documents-thunks.ts | 13 ++++-- .../complaints/export-complaint-input.ts | 8 ++++ 11 files changed, 146 insertions(+), 20 deletions(-) create mode 100644 backend/src/types/models/complaints/export-complaint-parameters.ts create mode 100644 backend/src/types/models/general/attachment.ts create mode 100644 frontend/src/app/types/complaints/export-complaint-input.ts diff --git a/backend/src/common/methods.ts b/backend/src/common/methods.ts index b3868f680..afc7ce875 100644 --- a/backend/src/common/methods.ts +++ b/backend/src/common/methods.ts @@ -61,3 +61,37 @@ export const formatPhonenumber = (input: string): string => { //-- return the phone number as its stored return input; }; + +export const getFileType = (input: string): string => { + const extension = getFileExtension(input); + return mapExtensiontoFileType(extension); +}; + +export const getFileExtension = (input: string): string => { + return input + .substring(input.lastIndexOf(".") + 1) + .toLowerCase() + .trim(); +}; + +export const mapExtensiontoFileType = (input: string): string => { + if (["bmp", "gif", "heif", "heic", "jpg", "jpeg", "png", "psd", "svg", "tif", "tiff"].includes(input)) { + return "Image"; + } + if (["doc", "docx", "md", "odt", "pdf", "ppt", "rtf", "txt", "xls", "xlsx"].includes(input)) { + return "Document"; + } + if (["flac", "mp3", "aac", "ogg", "wma", "wav", "wave"].includes(input)) { + return "Audio"; + } + if (["avi", "flv", "mov", "mp4"].includes(input)) { + return "Video"; + } + if (["7z", "jar", "rar", "zip"].includes(input)) { + return "Archive"; + } + if (["eml", "msg", "ost", "pst"].includes(input)) { + return "Email"; + } + return "Unknown"; +}; diff --git a/backend/src/types/models/complaints/export-complaint-parameters.ts b/backend/src/types/models/complaints/export-complaint-parameters.ts new file mode 100644 index 000000000..1317369d6 --- /dev/null +++ b/backend/src/types/models/complaints/export-complaint-parameters.ts @@ -0,0 +1,8 @@ +import { COMPLAINT_TYPE } from "./complaint-type"; + +export interface ExportComplaintParameters { + id: string; + type: COMPLAINT_TYPE; + tz: string; + attachments: any; +} diff --git a/backend/src/types/models/general/attachment.ts b/backend/src/types/models/general/attachment.ts new file mode 100644 index 000000000..839fc6335 --- /dev/null +++ b/backend/src/types/models/general/attachment.ts @@ -0,0 +1,13 @@ +export interface Attachment { + type: AttachmentType; + date: Date; + name: string; + user: string; + sequenceId: number; + fileType: string; +} + +export enum AttachmentType { + COMPLAINT_ATTACHMENT = "COMPLAINT_ATTACHMENT", + OUTCOME_ATTACHMENT = "OUTCOME_ATTACHMENT", +} diff --git a/backend/src/v1/complaint/complaint.service.ts b/backend/src/v1/complaint/complaint.service.ts index 7216db94d..af0be3b67 100644 --- a/backend/src/v1/complaint/complaint.service.ts +++ b/backend/src/v1/complaint/complaint.service.ts @@ -74,7 +74,8 @@ import { CompMthdRecvCdAgcyCdXrefService } from "../comp_mthd_recv_cd_agcy_cd_xr import { OfficerService } from "../officer/officer.service"; import { SpeciesCode } from "../species_code/entities/species_code.entity"; import { LinkedComplaintXrefService } from "../linked_complaint_xref/linked_complaint_xref.service"; - +import { Attachment, AttachmentType } from "../../types/models/general/attachment"; +import { getFileType } from "../../common/methods"; const WorldBounds: Array = [-180, -90, 180, 90]; type complaintAlias = HwcrComplaint | AllegationComplaint | GirComplaint; @Injectable({ scope: Scope.REQUEST }) @@ -1717,7 +1718,13 @@ export class ComplaintService { return results; }; - getReportData = async (id: string, complaintType: COMPLAINT_TYPE, tz: string, token: string) => { + getReportData = async ( + id: string, + complaintType: COMPLAINT_TYPE, + tz: string, + token: string, + attachments: Attachment[], + ) => { let data; mapWildlifeReport(this.mapper, tz); mapAllegationReport(this.mapper, tz); @@ -2184,6 +2191,29 @@ export class ComplaintService { if (data.incidentDateTime) { data.incidentDateTime = _applyTimezone(data.incidentDateTime, tz, "datetime"); } + // Using short names like "cAtts" and "oAtts" to fit them in CDOGS template table cells + data.cAtts = attachments + .filter((item) => item.type === AttachmentType.COMPLAINT_ATTACHMENT) + .map((item) => { + return { + name: item.name, + date: _applyTimezone(item.date, tz, "datetime"), + fileType: getFileType(item.name), + }; + }); + data.hasComplaintAttachments = data.cAtts?.length > 0; + + data.oAtts = attachments + .filter((item) => item.type === AttachmentType.OUTCOME_ATTACHMENT) + .map((item) => { + return { + name: item.name, + date: _applyTimezone(item.date, tz, "datetime"), + fileType: getFileType(item.name), + }; + }); + + data.hasOutcomeAttachments = data.oAtts?.length > 0; return data; } catch (error) { diff --git a/backend/src/v1/document/document.controller.ts b/backend/src/v1/document/document.controller.ts index 4311e3d23..7e1c3ac07 100644 --- a/backend/src/v1/document/document.controller.ts +++ b/backend/src/v1/document/document.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, Logger, Param, Query, Res, UseGuards } from "@nestjs/common"; +import { Body, Controller, Logger, Post, Res, UseGuards } from "@nestjs/common"; import { Response } from "express"; import { DocumentService } from "./document.service"; import { JwtRoleGuard } from "../../auth/jwtrole.guard"; @@ -9,6 +9,8 @@ import { Token } from "../../auth/decorators/token.decorator"; import { COMPLAINT_TYPE } from "../../types/models/complaints/complaint-type"; import { format } from "date-fns"; import { escape } from "escape-html"; +import { ExportComplaintParameters } from "src/types/models/complaints/export-complaint-parameters"; +import { Attachment, AttachmentType } from "../../types/models/general/attachment"; @UseGuards(JwtRoleGuard) @ApiTags("document") @@ -18,18 +20,38 @@ export class DocumentController { constructor(private readonly service: DocumentService) {} - @Get("/export-complaint/:type") + @Post("/export-complaint") @Roles(Role.COS_OFFICER, Role.CEEB) - async exportComplaint( - @Param("type") type: COMPLAINT_TYPE, - @Query("id") id: string, - @Query("tz") tz: string, - @Token() token, - @Res() res: Response, - ): Promise { + async exportComplaint(@Body() model: ExportComplaintParameters, @Token() token, @Res() res: Response): Promise { + const id: string = model?.id ?? "unknown"; + + const complaintsAttachments = model?.attachments?.complaintsAttachments ?? []; + const outcomeAttachments = model?.attachments?.outcomeAttachments ?? []; + + const attachments: Attachment[] = [ + ...complaintsAttachments.map((item, index) => { + return { + type: AttachmentType.COMPLAINT_ATTACHMENT, + user: item.createdBy, + name: decodeURIComponent(item.name), + date: item.createdAt, + sequenceId: index, + } as Attachment; + }), + ...outcomeAttachments.map((item, index) => { + return { + type: AttachmentType.OUTCOME_ATTACHMENT, + date: item.createdAt, + name: decodeURIComponent(item.name), + user: item.createdBy, + sequenceId: index, + } as Attachment; + }), + ]; + try { - const fileName = `Complaint-${id}-${type}-${format(new Date(), "yyyy-MM-dd")}.pdf`; - const response = await this.service.exportComplaint(id, type, fileName, tz, token); + const fileName = `Complaint-${id}-${model.type}-${format(new Date(), "yyyy-MM-dd")}.pdf`; + const response = await this.service.exportComplaint(id, model.type, fileName, model.tz, token, attachments); if (!response || !response.data) { throw Error(`exception: unable to export document for complaint: ${id}`); diff --git a/backend/src/v1/document/document.service.ts b/backend/src/v1/document/document.service.ts index 6a2f35b50..6597f6de9 100644 --- a/backend/src/v1/document/document.service.ts +++ b/backend/src/v1/document/document.service.ts @@ -2,6 +2,7 @@ import { Inject, Injectable, Logger } from "@nestjs/common"; import { CdogsService } from "../../external_api/cdogs/cdogs.service"; import { ComplaintService } from "../complaint/complaint.service"; import { COMPLAINT_TYPE } from "../../types/models/complaints/complaint-type"; +import { Attachment } from "src/types/models/general/attachment"; @Injectable() export class DocumentService { @@ -17,11 +18,18 @@ export class DocumentService { //-- using the cdogs api generate a new document from the specified //-- complaint-id and complaint type //-- - exportComplaint = async (id: string, type: COMPLAINT_TYPE, name: string, tz: string, token: string) => { + exportComplaint = async ( + id: string, + type: COMPLAINT_TYPE, + name: string, + tz: string, + token: string, + attachments?: Attachment[], + ) => { try { //-- get the complaint from the system, but do not include anything other //-- than the base complaint. no maps, no attachments, no outcome data - const data = await this.ceds.getReportData(id, type, tz, token); + const data = await this.ceds.getReportData(id, type, tz, token, attachments); //-- return await this.cdogs.generate(name, data, type); diff --git a/backend/templates/complaint/CDOGS-CEEB-COMPLAINT-TEMPLATE-v1.docx b/backend/templates/complaint/CDOGS-CEEB-COMPLAINT-TEMPLATE-v1.docx index 26a5fa60737021a3991b6788fbc03c390d594988..06a0d4dc4732a23e5934dc68bd30a7a0665d2c71 100644 GIT binary patch delta 18011 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 HpuhhB;~^!h delta 17215 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 diff --git a/backend/templates/complaint/CDOGS-ERS-COMPLAINT-TEMPLATE-v1.docx b/backend/templates/complaint/CDOGS-ERS-COMPLAINT-TEMPLATE-v1.docx index 6e08cbc30b31f4ff783fa392057bf96ff8df395f..f3bd956d86f32ce598d60f916b8f24f3764604db 100644 GIT binary patch delta 17369 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�?Z-Ay;%TaKXX+#UlBq&@&!#Hd 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

@kE5NC#GP_IOE+ei3wpNe+!dOMSGc7XJ` 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%0DhTf@#+mALg_@rPx3y} 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 JLhu{({{Y4$jgbHV diff --git a/frontend/src/app/store/reducers/documents-thunks.ts b/frontend/src/app/store/reducers/documents-thunks.ts index 92c736b6d..87e5d8d92 100644 --- a/frontend/src/app/store/reducers/documents-thunks.ts +++ b/frontend/src/app/store/reducers/documents-thunks.ts @@ -5,13 +5,15 @@ import { format } from "date-fns"; import axios, { AxiosRequestConfig } from "axios"; import { AUTH_TOKEN, getUserAgency } from "@service/user-service"; import { AgencyType } from "@apptypes/app/agency-types"; +import { ExportComplaintInput } from "@/app/types/complaints/export-complaint-input"; //-- //-- exports a complaint as a pdf document //-- export const exportComplaint = (type: string, id: string): ThunkAction, RootState, unknown, Action> => - async (dispatch) => { + async (dispatch, getState) => { + const { attachments } = getState(); try { const agency = getUserAgency(); let tailored_filename = ""; @@ -38,7 +40,7 @@ export const exportComplaint = tailored_filename = `Complaint-${id}-${type}-${format(new Date(), "yyyy-MM-dd")}.pdf`; } - const tz: string = encodeURIComponent(Intl.DateTimeFormat().resolvedOptions().timeZone); + const tz: string = Intl.DateTimeFormat().resolvedOptions().timeZone; const axiosConfig: AxiosRequestConfig = { responseType: "arraybuffer", // Specify response type as arraybuffer @@ -46,10 +48,11 @@ export const exportComplaint = axios.defaults.headers.common["Authorization"] = `Bearer ${localStorage.getItem(AUTH_TOKEN)}`; - const url = `${config.API_BASE_URL}/v1/document/export-complaint/${type}?id=${id}&tz=${tz}`; + const exportComplaintInput = { id, type, tz, attachments } as ExportComplaintInput; - //-- this should not work as there's no authentication token passed to the server, - const response = await axios.get(url, axiosConfig); + const url = `${config.API_BASE_URL}/v1/document/export-complaint`; + + const response = await axios.post(url, exportComplaintInput, axiosConfig); //-- this is a janky solution, but as of 2024 it is still the widly //-- accepted solution to download a file from a service diff --git a/frontend/src/app/types/complaints/export-complaint-input.ts b/frontend/src/app/types/complaints/export-complaint-input.ts new file mode 100644 index 000000000..b9fa89539 --- /dev/null +++ b/frontend/src/app/types/complaints/export-complaint-input.ts @@ -0,0 +1,8 @@ +import { AttachmentsState } from "../state/attachments-state"; + +export interface ExportComplaintInput { + id: string; + type: string; + tz: string; + attachments: AttachmentsState; +} From 95ec407146e49ac1471b44cd8791491dd220df22 Mon Sep 17 00:00:00 2001 From: Jonathan Funk Date: Wed, 18 Dec 2024 10:31:47 -0800 Subject: [PATCH 11/28] add in fix for granting public schema --- .github/actions/dbdeployer/action.yml | 166 ++++++++++++++++++++++++++ .github/workflows/pr-close.yml | 47 ++++++++ .github/workflows/pr-open.yml | 4 +- charts/crunchy/templates/cm.yaml | 9 ++ 4 files changed, 224 insertions(+), 2 deletions(-) create mode 100644 .github/actions/dbdeployer/action.yml create mode 100644 charts/crunchy/templates/cm.yaml diff --git a/.github/actions/dbdeployer/action.yml b/.github/actions/dbdeployer/action.yml new file mode 100644 index 000000000..863861149 --- /dev/null +++ b/.github/actions/dbdeployer/action.yml @@ -0,0 +1,166 @@ +name: .Crunchy Deploy + +on: + workflow_call: + inputs: ### Required + directory: + description: Crunchy Chart directory + default: 'charts/crunchy' + required: false + type: string + oc_server: + default: https://api.silver.devops.gov.bc.ca:6443 + description: 'OpenShift server' + required: false + type: string + environment: + description: Environment name; omit for PRs + required: false + type: string + s3_enabled: + description: Enable S3 backups + required: false + default: true + type: boolean + values: + description: 'Values file' + default: 'values.yaml' + required: false + type: string + app_values: + description: 'App specific values file which is present inside charts/app' + default: 'values.yaml' + required: false + type: string + enabled: + description: 'Enable the deployment of the crunchy database, easy switch to turn it on/off' + default: true + required: false + type: boolean + timeout-minutes: + description: 'Timeout minutes' + default: 20 + required: false + type: number + triggers: + description: Paths used to trigger a database deployment + required: false + type: string + secrets: + oc_namespace: + description: OpenShift namespace + required: true + oc_token: + description: OpenShift token + required: true + s3_access_key: + description: S3 access key + required: false + s3_secret_key: + description: S3 secret key + required: false +jobs: + deploy_db: + timeout-minutes: ${{ inputs.timeout-minutes }} + runs-on: ubuntu-24.04 + if: ${{ inputs.enabled }} + name: Deploy Or Upgrade Crunchy DB + environment: ${{ inputs.environment }} + steps: + - uses: actions/checkout@v4 + - name: Install CLI tools from OpenShift Mirror + uses: redhat-actions/openshift-tools-installer@v1 + with: + oc: "4.14.37" + - uses: bcgov-nr/action-diff-triggers@v0.2.0 + id: triggers + with: + triggers: ${{ inputs.triggers }} + - name: Validate Inputs + if: steps.triggers.outputs.triggered == 'true' + shell: bash + run: | + if [ ${{ inputs.s3_enabled }} == true ]; then + echo "S3 ie enabled for backups, checking for mandatory secrets" + if [ ! "${{ secrets.s3_access_key }}" ]; then + echo "S3 access key not found" + exit 1 + fi + if [ ! "${{ secrets.s3_secret_key }}" ]; then + echo "S3 secret key not found" + exit 1 + fi + fi + + - name: OC Login + shell: bash + run: | + # OC Login + OC_TEMP_TOKEN=$(curl -k -X POST ${{ inputs.oc_server }}/api/v1/namespaces/${{ secrets.oc_namespace }}/serviceaccounts/pipeline/token --header "Authorization: Bearer ${{ secrets.oc_token }}" -d '{"spec": {"expirationSeconds": 600}}' -H 'Content-Type: application/json; charset=utf-8' | jq -r '.status.token' ) + + oc login --token=$OC_TEMP_TOKEN --server=${{ inputs.oc_server }} + oc project ${{ secrets.oc_namespace }} # Safeguard! + + - name: Deploy Database + if: steps.triggers.outputs.triggered == 'true' + working-directory: ${{ inputs.directory }} + shell: bash + run: | + echo 'Deploying crunchy helm chart' + if [ ${{ inputs.s3_enabled }} == true ]; then + helm upgrade --install --wait --set crunchy.pgBackRest.s3.enabled=true \ + --set-string crunchy.pgBackRest.s3.accessKey=${{ secrets.s3_access_key }} \ + --set-string crunchy.pgBackRest.s3.secretKey=${{ secrets.s3_secret_key }} \ + --values ${{ inputs.values }} postgres . + else + helm upgrade --install --wait --values ${{ inputs.values }} postgres . + fi + + - name: Add PR specific user to Crunchy DB # only for PRs + shell: bash + if: github.event_name == 'pull_request' + run: | + echo 'Adding PR specific user to Crunchy DB' + NEW_USER='{"databases":["app-${{ github.event.number }}"],"name":"app-${{ github.event.number }}"}' + CURRENT_USERS=$(oc get PostgresCluster/postgres-crunchy -o json | jq '.spec.users') + echo "${CURRENT_USERS}" + + # check if current_users already contains the new_user + if echo "${CURRENT_USERS}" | jq -e ".[] | select(.name == \"app-${{ github.event.number }}\")" > /dev/null; then + echo "User already exists" + exit 0 + fi + + UPDATED_USERS=$(echo "$CURRENT_USERS" | jq --argjson NEW_USER "$NEW_USER" '. + [$NEW_USER]') + echo "$UPDATED_USERS" + PATCH_JSON=$(jq -n --argjson users "$UPDATED_USERS" '{"spec": {"users": $users}}') + echo "$PATCH_JSON" + oc patch PostgresCluster/postgres-crunchy --type=merge -p "${PATCH_JSON}" + + # wait for sometime as it takes time to create the user, query the secret and check if it is created, otherwise wait in a loop for 10 rounds + for i in {1..10}; do + if oc get secret postgres-crunchy-pguser-app-${{ github.event.number }} -o jsonpath='{.metadata.name}' > /dev/null; then + echo "Secret created" + break + else + echo "Secret not created, waiting for 60 seconds" + sleep 60 + fi + done + + # Add public schema and grant to PR user + # get primary crunchy pod and remove the role and db + CRUNCHY_PG_PRIMARY_POD_NAME=$(oc get pods -l postgres-operator.crunchydata.com/role=master -o json | jq -r '.items[0].metadata.name') + echo "${CRUNCHY_PG_PRIMARY_POD_NAME}" + oc exec "${CRUNCHY_PG_PRIMARY_POD_NAME}" -- psql -d "app-${{ github.event.number }}" -c "CREATE SCHEMA IF NOT EXISTS public;" + oc exec "${CRUNCHY_PG_PRIMARY_POD_NAME}" -- psql -d "app-${{ github.event.number }}" -c "GRANT ALL PRIVILEGES ON SCHEMA public TO \"app-${{ github.event.number }}\";" + oc exec "${CRUNCHY_PG_PRIMARY_POD_NAME}" -- psql -d "app-${{ github.event.number }}" -c "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO \"app-${{ github.event.number }}\";" + oc exec "${CRUNCHY_PG_PRIMARY_POD_NAME}" -- psql -d "app-${{ github.event.number }}" -c "GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO \"app-${{ github.event.number }}\";" + oc exec "${CRUNCHY_PG_PRIMARY_POD_NAME}" -- psql -d "app-${{ github.event.number }}" -c "GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA public TO \"app-${{ github.event.number }}\";" + oc exec "${CRUNCHY_PG_PRIMARY_POD_NAME}" -- psql -d "app-${{ github.event.number }}" -c "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON TABLES TO \"app-${{ github.event.number }}\";" + oc exec "${CRUNCHY_PG_PRIMARY_POD_NAME}" -- psql -d "app-${{ github.event.number }}" -c "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON SEQUENCES TO \"app-${{ github.event.number }}\";" + oc exec "${CRUNCHY_PG_PRIMARY_POD_NAME}" -- psql -d "app-${{ github.event.number }}" -c "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON FUNCTIONS TO \"app-${{ github.event.number }}\";" + # TODO: remove these + + + diff --git a/.github/workflows/pr-close.yml b/.github/workflows/pr-close.yml index a0b95c61c..dab68bd2e 100644 --- a/.github/workflows/pr-close.yml +++ b/.github/workflows/pr-close.yml @@ -42,3 +42,50 @@ jobs: OC_SERVER: ${{ vars.OC_SERVER }} OC_TOKEN: ${{ secrets.OC_TOKEN }} PR_NUMBER: ${{ github.event.number }} + + cleanup-crunchy: + name: Cleanup Ephemeral Crunchy Data + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + # OC setup + - uses: redhat-actions/openshift-tools-installer@v1 + with: + oc: "4" + + # OC Login + - run: | + # OC Login + oc login --token=${{ secrets.oc_token }} --server=${{ inputs.oc_server }} + oc project ${{ secrets.oc_namespace }} # Safeguard! + - run: | + # check if postgres-crunchy exists or else exit + oc get PostgresCluster/postgres-crunchy || exit 0 + # Remove the user from the crunchy cluster yaml and apply the changes + USER_TO_REMOVE='{"databases":["app-${{ github.event.number }}"],"name":"app-${{ github.event.number }}"}' + + echo 'getting current users from crunchy' + CURRENT_USERS=$(oc get PostgresCluster/postgres-crunchy -o json | jq '.spec.users') + echo "${CURRENT_USERS}" + + # Remove the user from the list, + UPDATED_USERS=$(echo "$CURRENT_USERS" | jq --argjson user "$USER_TO_REMOVE" 'map(select(. != $user))') + + PATCH_JSON=$(jq -n --argjson users "$UPDATED_USERS" '{"spec": {"users": $users}}') + oc patch PostgresCluster/postgres-crunchy --type=merge -p "$PATCH_JSON" + + # get primary crunchy pod and remove the role and db + CRUNCHY_PG_PRIMARY_POD_NAME=$(oc get pods -l postgres-operator.crunchydata.com/role=master -o json | jq -r '.items[0].metadata.name') + + echo "${CRUNCHY_PG_PRIMARY_POD_NAME}" + # Terminate all connections to the database before trying terminate + oc exec "${CRUNCHY_PG_PRIMARY_POD_NAME}" -- psql -c "SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = 'app-${{ github.event.number }}' AND pid <> pg_backend_pid();" + + # Drop the database and role + oc exec "${CRUNCHY_PG_PRIMARY_POD_NAME}" -- psql -c "DROP DATABASE \"app-${{ github.event.number }}\" --cascade" + + oc exec "${CRUNCHY_PG_PRIMARY_POD_NAME}" -- psql -c "DROP ROLE \"app-${{ github.event.number }}\" --cascade" + + echo "Database and Role for PR is cleaned." + + exit 0 \ No newline at end of file diff --git a/.github/workflows/pr-open.yml b/.github/workflows/pr-open.yml index 1318132e3..8eb39c40b 100644 --- a/.github/workflows/pr-open.yml +++ b/.github/workflows/pr-open.yml @@ -35,8 +35,8 @@ jobs: # https://github.com/bcgov/quickstart-openshift crunchy: name: Deploy Crunchy - # needs: [builds] - uses: bcgov/quickstart-openshift/.github/workflows/.deployer-db.yml@8b9c1c1c1b3417007fb746595680a5df12ac1204 + needs: [builds] + uses: ./.github/actions/dbdeployer secrets: oc_namespace: ${{ secrets.OC_NAMESPACE }} oc_token: ${{ secrets.OC_TOKEN }} diff --git a/charts/crunchy/templates/cm.yaml b/charts/crunchy/templates/cm.yaml new file mode 100644 index 000000000..f765947ce --- /dev/null +++ b/charts/crunchy/templates/cm.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "crunchy-postgres.fullname" . }} + labels: {{ include "crunchy-postgres.labels" . | nindent 4 }} +data: + bootstrap.sql: | + CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA public; \ No newline at end of file From adba030194f1e3d69dda8fdf0ee4218860a578ce Mon Sep 17 00:00:00 2001 From: Jonathan Funk Date: Wed, 18 Dec 2024 10:41:35 -0800 Subject: [PATCH 12/28] tag dbdeployer --- .github/workflows/pr-open.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-open.yml b/.github/workflows/pr-open.yml index 8eb39c40b..313a64e91 100644 --- a/.github/workflows/pr-open.yml +++ b/.github/workflows/pr-open.yml @@ -36,7 +36,7 @@ jobs: crunchy: name: Deploy Crunchy needs: [builds] - uses: ./.github/actions/dbdeployer + uses: ./.github/actions/dbdeployer@CE-596 # TODO: remove tag after testing secrets: oc_namespace: ${{ secrets.OC_NAMESPACE }} oc_token: ${{ secrets.OC_TOKEN }} From 6d176bb4e5e810d209c67e86a49e12f548ae13b9 Mon Sep 17 00:00:00 2001 From: Jonathan Funk Date: Wed, 18 Dec 2024 12:39:52 -0800 Subject: [PATCH 13/28] test --- .github/workflows/pr-open.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-open.yml b/.github/workflows/pr-open.yml index 313a64e91..8eb39c40b 100644 --- a/.github/workflows/pr-open.yml +++ b/.github/workflows/pr-open.yml @@ -36,7 +36,7 @@ jobs: crunchy: name: Deploy Crunchy needs: [builds] - uses: ./.github/actions/dbdeployer@CE-596 # TODO: remove tag after testing + uses: ./.github/actions/dbdeployer secrets: oc_namespace: ${{ secrets.OC_NAMESPACE }} oc_token: ${{ secrets.OC_TOKEN }} From 3d60c5dbe8a5c2bb75adc893b5302ec2cb57354a Mon Sep 17 00:00:00 2001 From: Jonathan Funk Date: Wed, 18 Dec 2024 12:49:22 -0800 Subject: [PATCH 14/28] fix dbdeployer flow --- .../dbdeployer/action.yml => workflows/.dbdeployer.yml} | 0 .github/workflows/pr-open.yml | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename .github/{actions/dbdeployer/action.yml => workflows/.dbdeployer.yml} (100%) diff --git a/.github/actions/dbdeployer/action.yml b/.github/workflows/.dbdeployer.yml similarity index 100% rename from .github/actions/dbdeployer/action.yml rename to .github/workflows/.dbdeployer.yml diff --git a/.github/workflows/pr-open.yml b/.github/workflows/pr-open.yml index 8eb39c40b..419a278c3 100644 --- a/.github/workflows/pr-open.yml +++ b/.github/workflows/pr-open.yml @@ -36,7 +36,7 @@ jobs: crunchy: name: Deploy Crunchy needs: [builds] - uses: ./.github/actions/dbdeployer + uses: ./.github/workflows/.dbdeployer.yml secrets: oc_namespace: ${{ secrets.OC_NAMESPACE }} oc_token: ${{ secrets.OC_TOKEN }} From 4eef18d56211a5e1fdb97e01f2dc87fdadab9eeb Mon Sep 17 00:00:00 2001 From: Jonathan Funk Date: Wed, 18 Dec 2024 13:00:25 -0800 Subject: [PATCH 15/28] fix bad merge --- .../complaint-map-with-server-side-clustering.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/frontend/src/app/components/containers/complaints/complaint-map-with-server-side-clustering.tsx b/frontend/src/app/components/containers/complaints/complaint-map-with-server-side-clustering.tsx index 03ae19384..25731b468 100644 --- a/frontend/src/app/components/containers/complaints/complaint-map-with-server-side-clustering.tsx +++ b/frontend/src/app/components/containers/complaints/complaint-map-with-server-side-clustering.tsx @@ -7,7 +7,6 @@ import { ComplaintRequestPayload } from "@/app/types/complaints/complaint-filter import LeafletMapWithServerSideClustering from "@components/mapping/leaflet-map-with-server-side-clustering"; import { generateApiParameters, get } from "@common/api"; import config from "@/config"; - import { selectComplaintSearchParameters, setComplaint, @@ -24,7 +23,6 @@ type Props = { export const generateMapComplaintRequestPayload = ( complaintType: string, filters: ComplaintFilters, - page: number, pageSize: number, sortColumn: string, @@ -32,8 +30,6 @@ export const generateMapComplaintRequestPayload = ( searchQuery: string, ): ComplaintRequestPayload => { const { - sortColumn, - sortOrder, region, zone, community, @@ -201,4 +197,4 @@ export const ComplaintMapWithServerSideClustering: FC = ({ type, searchQu unmappedCount={unmappedCount} /> ); -}; +}; \ No newline at end of file From f51236716a83c0aa679af141f06516f477222944 Mon Sep 17 00:00:00 2001 From: Jonathan Funk Date: Wed, 18 Dec 2024 13:10:19 -0800 Subject: [PATCH 16/28] disable s3 in dev --- .github/workflows/pr-open.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pr-open.yml b/.github/workflows/pr-open.yml index 419a278c3..f8c98edae 100644 --- a/.github/workflows/pr-open.yml +++ b/.github/workflows/pr-open.yml @@ -37,6 +37,8 @@ jobs: name: Deploy Crunchy needs: [builds] uses: ./.github/workflows/.dbdeployer.yml + with: + s3_enabled: false # no backups in dev secrets: oc_namespace: ${{ secrets.OC_NAMESPACE }} oc_token: ${{ secrets.OC_TOKEN }} From 3f1ac16796cfba1b3340f320164d35be1ca5f717 Mon Sep 17 00:00:00 2001 From: Jonathan Funk Date: Wed, 18 Dec 2024 13:55:52 -0800 Subject: [PATCH 17/28] cleanup close --- .github/workflows/pr-close.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-close.yml b/.github/workflows/pr-close.yml index dab68bd2e..f7ff62e4e 100644 --- a/.github/workflows/pr-close.yml +++ b/.github/workflows/pr-close.yml @@ -56,8 +56,8 @@ jobs: # OC Login - run: | # OC Login - oc login --token=${{ secrets.oc_token }} --server=${{ inputs.oc_server }} - oc project ${{ secrets.oc_namespace }} # Safeguard! + oc login --token=${{ secrets.OC_TOKEN }} --server=${{ vars.OC_SERVER }} + oc project ${{ secrets.OC_NAMESPACE }} # Safeguard! - run: | # check if postgres-crunchy exists or else exit oc get PostgresCluster/postgres-crunchy || exit 0 From e0f484d4c0477811860c40d32639408ed3950125 Mon Sep 17 00:00:00 2001 From: Jonathan Funk Date: Wed, 18 Dec 2024 15:24:00 -0800 Subject: [PATCH 18/28] add crunchy toggle --- .github/workflows/pr-open.yml | 3 ++ .../backend/templates/deployment.yaml | 31 +++++++++++++++++++ charts/app/values.yaml | 2 ++ 3 files changed, 36 insertions(+) diff --git a/.github/workflows/pr-open.yml b/.github/workflows/pr-open.yml index f8c98edae..ac5fa54db 100644 --- a/.github/workflows/pr-open.yml +++ b/.github/workflows/pr-open.yml @@ -51,6 +51,9 @@ jobs: secrets: oc_namespace: ${{ secrets.OC_NAMESPACE }} oc_token: ${{ secrets.OC_TOKEN }} + with: + params: --set global.useCrunchy=true + healthcheck: name: Healthcheck Deployment diff --git a/charts/app/templates/backend/templates/deployment.yaml b/charts/app/templates/backend/templates/deployment.yaml index 85b291419..af0f7a40e 100644 --- a/charts/app/templates/backend/templates/deployment.yaml +++ b/charts/app/templates/backend/templates/deployment.yaml @@ -31,9 +31,11 @@ spec: - name: {{ include "backend.fullname" . }}-init image: "{{ .Values.global.registry }}/{{ .Values.global.repository }}/migrations:{{ .Values.global.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ default "Always" .Values.backend.imagePullPolicy }} + {{- if not .Values.global.useCrunchy }} envFrom: - secretRef: name: {{ .Release.Name }}-flyway + {{- end }} env: - name: NODE_TLS_REJECT_UNAUTHORIZED value: "0" @@ -45,6 +47,13 @@ spec: value: "10" - name: FLYWAY_LOCATIONS value: "{{- if eq .Release.Namespace "c1c7ed-dev" -}}{{ .Values.global.secrets.flywayLocations.dev }}{{- else if eq .Release.Namespace "c1c7ed-test" -}}{{ .Values.global.secrets.flywayLocations.test }}{{- else if eq .Release.Namespace "c1c7ed-prod" -}}{{ .Values.global.secrets.flywayLocations.prod }}{{- else -}}filesystem:./flyway/sql{{- end }}" + {{- if .Values.global.useCrunchy }} + - name: FLYWAY_URL + valueFrom: + secretKeyRef: + name: postgres-crunchy-pguser-app-{{ .Values.global.tag | quote }} + key: pgbouncer-jdbc-uri + {{- end }} resources: limits: cpu: 200m @@ -68,6 +77,28 @@ spec: value: info - name: NODE_TLS_REJECT_UNAUTHORIZED value: "0" + {{- if .Values.global.useCrunchy }} + - name: POSTGRESQL_DATABASE + valueFrom: + secretKeyRef: + name: postgres-crunchy-pguser-app-{{ .Values.global.tag | quote }} + key: dbname + - name: POSTGRESQL_HOST + valueFrom: + secretKeyRef: + name: postgres-crunchy-pguser-app-{{ .Values.global.tag | quote }} + key: pgbouncer-host + - name: POSTGRESQL_USER + valueFrom: + secretKeyRef: + name: postgres-crunchy-pguser-app-{{ .Values.global.tag | quote }} + key: user + - name: POSTGRESQL_PASSWORD + valueFrom: + secretKeyRef: + name: postgres-crunchy-pguser-app-{{ .Values.global.tag | quote }} + key: password + {{- end }} ports: - name: http containerPort: {{ .Values.backend.service.targetPort }} diff --git a/charts/app/values.yaml b/charts/app/values.yaml index 073461f97..9b24db316 100644 --- a/charts/app/values.yaml +++ b/charts/app/values.yaml @@ -68,6 +68,8 @@ global: domain: "apps.silver.devops.gov.bc.ca" # it is apps.gold.devops.gov.bc.ca for gold cluster #-- the database Alias gives a nice way to switch to different databases, crunchy, patroni ... etc. databaseAlias: bitnami-pg + #-- use crunchy for the database, it is optional + useCrunchy: false #-- the components of the application, backend. backend: From 50a19eb81ba825ab6bed85a8cddf9db0f58c1d0b Mon Sep 17 00:00:00 2001 From: Jonathan Funk Date: Wed, 18 Dec 2024 15:37:39 -0800 Subject: [PATCH 19/28] fix tag quotes --- charts/app/templates/backend/templates/deployment.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/charts/app/templates/backend/templates/deployment.yaml b/charts/app/templates/backend/templates/deployment.yaml index af0f7a40e..a21599ffe 100644 --- a/charts/app/templates/backend/templates/deployment.yaml +++ b/charts/app/templates/backend/templates/deployment.yaml @@ -81,22 +81,22 @@ spec: - name: POSTGRESQL_DATABASE valueFrom: secretKeyRef: - name: postgres-crunchy-pguser-app-{{ .Values.global.tag | quote }} + name: postgres-crunchy-pguser-app-{{ .Values.global.tag }} key: dbname - name: POSTGRESQL_HOST valueFrom: secretKeyRef: - name: postgres-crunchy-pguser-app-{{ .Values.global.tag | quote }} + name: postgres-crunchy-pguser-app-{{ .Values.global.tag }} key: pgbouncer-host - name: POSTGRESQL_USER valueFrom: secretKeyRef: - name: postgres-crunchy-pguser-app-{{ .Values.global.tag | quote }} + name: postgres-crunchy-pguser-app-{{ .Values.global.tag }} key: user - name: POSTGRESQL_PASSWORD valueFrom: secretKeyRef: - name: postgres-crunchy-pguser-app-{{ .Values.global.tag | quote }} + name: postgres-crunchy-pguser-app-{{ .Values.global.tag }} key: password {{- end }} ports: From c1748acabe2993d4e737b838fe87c302dbfff972 Mon Sep 17 00:00:00 2001 From: Jonathan Funk Date: Wed, 18 Dec 2024 15:52:57 -0800 Subject: [PATCH 20/28] add quote trim for tags --- charts/app/templates/backend/templates/deployment.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/charts/app/templates/backend/templates/deployment.yaml b/charts/app/templates/backend/templates/deployment.yaml index a21599ffe..a5023538b 100644 --- a/charts/app/templates/backend/templates/deployment.yaml +++ b/charts/app/templates/backend/templates/deployment.yaml @@ -81,22 +81,22 @@ spec: - name: POSTGRESQL_DATABASE valueFrom: secretKeyRef: - name: postgres-crunchy-pguser-app-{{ .Values.global.tag }} + name: postgres-crunchy-pguser-app-{{ .Values.global.tag | trim "\"" }} key: dbname - name: POSTGRESQL_HOST valueFrom: secretKeyRef: - name: postgres-crunchy-pguser-app-{{ .Values.global.tag }} + name: postgres-crunchy-pguser-app-{{ .Values.global.tag | trim "\"" }} key: pgbouncer-host - name: POSTGRESQL_USER valueFrom: secretKeyRef: - name: postgres-crunchy-pguser-app-{{ .Values.global.tag }} + name: postgres-crunchy-pguser-app-{{ .Values.global.tag | trim "\"" }} key: user - name: POSTGRESQL_PASSWORD valueFrom: secretKeyRef: - name: postgres-crunchy-pguser-app-{{ .Values.global.tag }} + name: postgres-crunchy-pguser-app-{{ .Values.global.tag | trim "\"" }} key: password {{- end }} ports: From fa7dca493f4769a9c87584bd148dd934796fcaaa Mon Sep 17 00:00:00 2001 From: Jonathan Funk Date: Wed, 18 Dec 2024 16:07:43 -0800 Subject: [PATCH 21/28] trimall usage --- charts/app/templates/backend/templates/deployment.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/charts/app/templates/backend/templates/deployment.yaml b/charts/app/templates/backend/templates/deployment.yaml index a5023538b..9960109ff 100644 --- a/charts/app/templates/backend/templates/deployment.yaml +++ b/charts/app/templates/backend/templates/deployment.yaml @@ -81,22 +81,22 @@ spec: - name: POSTGRESQL_DATABASE valueFrom: secretKeyRef: - name: postgres-crunchy-pguser-app-{{ .Values.global.tag | trim "\"" }} + name: postgres-crunchy-pguser-app-{{ .Values.global.tag | trimAll "\"" }} key: dbname - name: POSTGRESQL_HOST valueFrom: secretKeyRef: - name: postgres-crunchy-pguser-app-{{ .Values.global.tag | trim "\"" }} + name: postgres-crunchy-pguser-app-{{ .Values.global.tag | trimAll "\"" }} key: pgbouncer-host - name: POSTGRESQL_USER valueFrom: secretKeyRef: - name: postgres-crunchy-pguser-app-{{ .Values.global.tag | trim "\"" }} + name: postgres-crunchy-pguser-app-{{ .Values.global.tag | trimAll "\"" }} key: user - name: POSTGRESQL_PASSWORD valueFrom: secretKeyRef: - name: postgres-crunchy-pguser-app-{{ .Values.global.tag | trim "\"" }} + name: postgres-crunchy-pguser-app-{{ .Values.global.tag | trimAll "\"" }} key: password {{- end }} ports: From ad4388cb9e1e4eebf09546bb4a0181a161f293f1 Mon Sep 17 00:00:00 2001 From: Jonathan Funk Date: Wed, 18 Dec 2024 16:17:02 -0800 Subject: [PATCH 22/28] fix flyway tag --- charts/app/templates/backend/templates/deployment.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/app/templates/backend/templates/deployment.yaml b/charts/app/templates/backend/templates/deployment.yaml index 9960109ff..86ab90393 100644 --- a/charts/app/templates/backend/templates/deployment.yaml +++ b/charts/app/templates/backend/templates/deployment.yaml @@ -51,7 +51,7 @@ spec: - name: FLYWAY_URL valueFrom: secretKeyRef: - name: postgres-crunchy-pguser-app-{{ .Values.global.tag | quote }} + name: postgres-crunchy-pguser-app-{{ .Values.global.tag | trimAll "\"" }} key: pgbouncer-jdbc-uri {{- end }} resources: From 47ee60a5a9143e602851bc60fb2312b7b29551cf Mon Sep 17 00:00:00 2001 From: Ryan Rondeau Date: Thu, 19 Dec 2024 10:02:49 -0800 Subject: [PATCH 23/28] feat: CE-1302 (#831) --- charts/app/templates/secret.yaml | 2 ++ .../R__create-replace-insert-action-taken.sql | 2 +- .../webeoc-scheduler/webeoc-scheduler.service.ts | 16 ++++------------ 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/charts/app/templates/secret.yaml b/charts/app/templates/secret.yaml index 423d23b0c..fb795d88c 100644 --- a/charts/app/templates/secret.yaml +++ b/charts/app/templates/secret.yaml @@ -44,6 +44,7 @@ {{- $webeocUrl := (get $secretData "webeocUrl" | b64dec | default "") }} {{- $webeocCronExpression := (get $secretData "webeocCronExpression" | b64dec | default "") }} {{- $webeocLogPath := (get $secretData "webeocLogPath" | b64dec | default "") }} +{{- $webeocDateFilter := (get $secretData "webeocDateFilter" | b64dec | default "2025-01-01T08:00:00Z") }} {{- $backupDir := (get $secretData "backupDir" | b64dec | default "") }} {{- $backupStrategy := (get $secretData "backupStrategy" | b64dec | default "") }} {{- $numBackups := (get $secretData "numBackups" | b64dec | default "") }} @@ -139,6 +140,7 @@ data: WEBEOC_URL: {{ $webeocUrl | b64enc | quote }} WEBEOC_CRON_EXPRESSION: {{ $webeocCronExpression | b64enc | quote }} WEBEOC_LOG_PATH: {{ $webeocLogPath | b64enc | quote }} + WEBEOC_DATE_FILTER: {{ $webeocDateFilter | b64enc | quote }} COMPLAINTS_API_KEY: {{ $caseManagementApiKey | b64enc | quote }} {{- end }} {{- if not (lookup "v1" "Secret" .Release.Namespace (printf "%s-flyway" .Release.Name)) }} diff --git a/migrations/migrations/R__create-replace-insert-action-taken.sql b/migrations/migrations/R__create-replace-insert-action-taken.sql index cb426165a..25a5e3b00 100644 --- a/migrations/migrations/R__create-replace-insert-action-taken.sql +++ b/migrations/migrations/R__create-replace-insert-action-taken.sql @@ -35,7 +35,7 @@ RAISE notice 'EXECUTING FUNCTION'; AND sc.staging_activity_code = action_taken_type; IF staged_data IS NULL THEN - RAISE notice 'NO COMPLAINT FOUND'; + -- RAISE notice 'NO COMPLAINT FOUND'; No complaint found is expected now for actions taken on complaints prior to Jan 1 2025 RETURN; END IF; diff --git a/webeoc/src/webeoc-scheduler/webeoc-scheduler.service.ts b/webeoc/src/webeoc-scheduler/webeoc-scheduler.service.ts index 4fa424ae9..a33f2b9ad 100644 --- a/webeoc/src/webeoc-scheduler/webeoc-scheduler.service.ts +++ b/webeoc/src/webeoc-scheduler/webeoc-scheduler.service.ts @@ -239,21 +239,13 @@ export class WebEocScheduler { private _filterComplaints(complaints: any[], flagName: string) { return complaints.filter((complaint: any) => { if (flagName === WEBEOC_FLAGS.COMPLAINTS) { - return ( + return (( complaint.flag_COS === "Yes" || complaint.violation_type === "Waste" || complaint.violation_type === "Pesticide" - ); - } else if (flagName === WEBEOC_FLAGS.COMPLAINT_UPDATES) { - return ( - complaint.flag_UPD === "Yes" || - complaint.update_violation_type === "Waste" || - complaint.update_violation_type === "Pesticide" - ); - } else if (flagName === WEBEOC_FLAGS.ACTIONS_TAKEN) { - return complaint.flag_AT === "Yes"; - } else if (flagName === WEBEOC_FLAGS.ACTIONS_TAKEN_UPDATES) { - return complaint.flag_UAT === "Yes"; + ) || (complaint.flag_COS !== "Yes" && Date.parse(`${complaint.incident_datetime} PST`) > Date.parse(process.env.WEBEOC_DATE_FILTER))); // 2025-01-01T08:00:00Z is midnight PST + } else { + return true; } }); } From 530dd8ae6e40b9aa0e9bb566a60b8081ff4a29f2 Mon Sep 17 00:00:00 2001 From: Ryan Rondeau Date: Thu, 19 Dec 2024 12:39:23 -0800 Subject: [PATCH 24/28] fix: fix bad merge fix (#833) Co-authored-by: afwilcox Co-authored-by: Scarlett <35635257+Scarlett-Truong@users.noreply.github.com> Co-authored-by: dmitri-korin-bcps <108112696+dmitri-korin-bcps@users.noreply.github.com> From 7b2d909bf41436beda1f75778d12a1cbd1f844ae Mon Sep 17 00:00:00 2001 From: Jonathan Funk Date: Tue, 7 Jan 2025 10:00:26 -0800 Subject: [PATCH 25/28] increase crunchy resourcing --- charts/crunchy/values.yaml | 56 +++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/charts/crunchy/values.yaml b/charts/crunchy/values.yaml index d833f42d4..70d9adede 100644 --- a/charts/crunchy/values.yaml +++ b/charts/crunchy/values.yaml @@ -32,16 +32,16 @@ crunchy: # enable it for TEST and PROD, for PR based pipelines simply use single prometheus.io/scrape: 'true' prometheus.io/port: '9187' dataVolumeClaimSpec: - storage: 200Mi + storage: 2Gi storageClassName: netapp-block-standard - walStorage: 255Mi + walStorage: 1Gi requests: - cpu: 50m - memory: 128Mi - limits: - cpu: 150m + cpu: 200m memory: 256Mi + limits: + cpu: 400m + memory: 512Mi replicaCertCopy: requests: cpu: 1m @@ -74,37 +74,37 @@ crunchy: # enable it for TEST and PROD, for PR based pipelines simply use single incrementalBackupSchedule: 0 0,12 * * * # every 12 hour incremental volume: accessModes: "ReadWriteOnce" - storage: 100Mi + storage: 3Gi storageClassName: netapp-file-backup config: requests: - cpu: 5m - memory: 32Mi - limits: - cpu: 20m + cpu: 15m memory: 64Mi + limits: + cpu: 60m + memory: 128Mi repoHost: requests: - cpu: 20m - memory: 128Mi - limits: - cpu: 50m + cpu: 60m memory: 256Mi + limits: + cpu: 150m + memory: 512Mi sidecars: requests: - cpu: 5m - memory: 16Mi - limits: cpu: 20m - memory: 64Mi + memory: 32Mi + limits: + cpu: 40m + memory: 128Mi jobs: requests: - cpu: 20m - memory: 128Mi - limits: cpu: 100m memory: 256Mi + limits: + cpu: 300m + memory: 512Mi patroni: postgresql: @@ -112,10 +112,10 @@ crunchy: # enable it for TEST and PROD, for PR based pipelines simply use single - "host all all 0.0.0.0/0 md5" - "host all all ::1/128 md5" parameters: - shared_buffers: 16MB # default is 128MB; a good tuned default for shared_buffers is 25% of the memory allocated to the pod - wal_buffers: "64kB" # this can be set to -1 to automatically set as 1/32 of shared_buffers or 64kB, whichever is larger + shared_buffers: 128MB # default is 128MB; a good tuned default for shared_buffers is 25% of the memory allocated to the pod + wal_buffers: -1 # this can be set to -1 to automatically set as 1/32 of shared_buffers or 64kB, whichever is larger min_wal_size: 32MB - max_wal_size: 64MB # default is 1GB + max_wal_size: 200MB # default is 1GB max_slot_wal_keep_size: 128MB # default is -1, allowing unlimited wal growth when replicas fall behind work_mem: 2MB # a work_mem value of 2 MB log_min_duration_statement: 1000ms # log queries taking more than 1 second to respond. @@ -127,11 +127,11 @@ crunchy: # enable it for TEST and PROD, for PR based pipelines simply use single image: # it's not necessary to specify an image as the images specified in the Crunchy Postgres Operator will be pulled by default replicas: 1 requests: - cpu: 5m - memory: 32Mi - limits: cpu: 20m memory: 64Mi + limits: + cpu: 40m + memory: 128Mi maxConnections: 10 # make sure less than postgres max connections # Postgres Cluster resource values: From f389db1eb24ec6bbee27e66e0ac4a9a086b586d3 Mon Sep 17 00:00:00 2001 From: afwilcox Date: Wed, 8 Jan 2025 10:55:08 -0800 Subject: [PATCH 26/28] chore: increase heap size --- backend/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index a7d9cda9f..1c1ef5d04 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -26,5 +26,5 @@ HEALTHCHECK --interval=30s --timeout=3s CMD curl -f http://localhost/:3000 # Non-privileged user USER app -# max old space the heap size, 120MB with 200MB limit in deployment. -CMD ["--max-old-space-size=120", "/app/dist/main"] +# max old space the heap size, 250MB +CMD ["--max-old-space-size=250", "/app/dist/main"] From a46444418abcf3742ed38eb37f91b97dbf318f02 Mon Sep 17 00:00:00 2001 From: afwilcox Date: Wed, 8 Jan 2025 12:52:40 -0800 Subject: [PATCH 27/28] chore: try increasing heap size again --- backend/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 1c1ef5d04..1dc5edeb6 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -26,5 +26,5 @@ HEALTHCHECK --interval=30s --timeout=3s CMD curl -f http://localhost/:3000 # Non-privileged user USER app -# max old space the heap size, 250MB -CMD ["--max-old-space-size=250", "/app/dist/main"] +# max old space the heap size, 500MB +CMD ["--max-old-space-size=500", "/app/dist/main"] From ea026e47865ee4d26aa7825bf726a145a5c13f6c Mon Sep 17 00:00:00 2001 From: afwilcox Date: Wed, 8 Jan 2025 16:29:03 -0800 Subject: [PATCH 28/28] chore: adjust request limits --- charts/app/templates/backend/templates/deployment.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/app/templates/backend/templates/deployment.yaml b/charts/app/templates/backend/templates/deployment.yaml index 1e61319af..990af921b 100644 --- a/charts/app/templates/backend/templates/deployment.yaml +++ b/charts/app/templates/backend/templates/deployment.yaml @@ -56,8 +56,8 @@ spec: {{- end }} resources: requests: - cpu: 50m - memory: 100Mi + cpu: 300m + memory: 750Mi containers: - name: {{ include "backend.fullname" . }} {{- if .Values.backend.securityContext }}