diff --git a/.github/actions/get-cache/action.yml b/.github/actions/get-cache/action.yml new file mode 100644 index 000000000000..a4f1184142d6 --- /dev/null +++ b/.github/actions/get-cache/action.yml @@ -0,0 +1,41 @@ +name: 'Get Cache Keys' +description: 'Action to get cache' +inputs: + github-token: + description: 'GitHub token' + required: true + enable-cache: + description: 'Enable cache' + default: '' + keys: + description: 'Keys' + default: 'false' +outputs: + keys: + description: 'Keys' + value: ${{ steps.prepare.outputs._CACHE_KEYS }} +runs: + using: 'composite' + steps: + - name: Adding required env vars + uses: actions/github-script@v7 + env: + github-token: ${{ inputs.GITHUB_TOKEN }} + with: + script: | + core.exportVariable('ACTIONS_CACHE_URL', 'https://cache.dev01.devland.is/') + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env['ACTIONS_RUNTIME_TOKEN']) + core.exportVariable('ACTIONS_RUNTIME_URL', process.env['ACTIONS_RUNTIME_URL']) + - name: Get cache + id: prepare + shell: bash + env: + _CACHE_KEYS: ${{ inputs.keys }} + ENABLE_CACHE: ${{ inputs.enable-cache }} + NODE_OPTIONS: --max-old-space-size=8192 + run: | + echo $_CACHE_KEYS + cd scripts/ci/cache + yarn install --immutable + node cache-action.mjs + echo $_CACHE_KEYS diff --git a/.github/actions/unit-test/action.yml b/.github/actions/unit-test/action.yml index dcea4d884e94..5a311af15f13 100644 --- a/.github/actions/unit-test/action.yml +++ b/.github/actions/unit-test/action.yml @@ -53,7 +53,7 @@ runs: - name: Cached codecov uploader id: codecov-cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: codecov key: ${{ runner.os }}-codeconv-${{ env.CODECOV_REV }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index bee2fbfacdf6..d9e874289fdc 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -25,7 +25,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. diff --git a/.github/workflows/config-values.yaml b/.github/workflows/config-values.yaml index af314b1368dd..ec68201b40a4 100644 --- a/.github/workflows/config-values.yaml +++ b/.github/workflows/config-values.yaml @@ -52,13 +52,13 @@ jobs: image: public.ecr.aws/m3u4c4h9/island-is/actions-runner-public:latest timeout-minutes: 5 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: ${{ github.event_name == 'pull_request' }} with: ref: ${{ github.event.pull_request.head.ref }} token: ${{ secrets.DIRTY_FIX_BOT_TOKEN }} - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: ${{ github.event_name != 'pull_request' }} - uses: actions/setup-node@v4 @@ -108,7 +108,7 @@ jobs: fail-fast: false matrix: ${{ fromJson(needs.prepare.outputs.ENVS) }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version-file: 'package.json' diff --git a/.github/workflows/external-checks.yml b/.github/workflows/external-checks.yml index f6bc4c684d56..5b35b5239f39 100644 --- a/.github/workflows/external-checks.yml +++ b/.github/workflows/external-checks.yml @@ -18,7 +18,7 @@ jobs: image: public.ecr.aws/m3u4c4h9/island-is/actions-runner-public:latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Check if codeowners file changed diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 9c5dcaa47d2c..ff461afdff3f 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -15,7 +15,6 @@ concurrency: env: COMPOSE_HTTP_TIMEOUT: 180 - GITHUB_ACTIONS_CACHE_URL: https://cache.dev01.devland.is/ SKIP_GENERATED_CACHE: ${{ contains(github.event.pull_request.labels.*.name, 'skip-generated-cache') }} NX_AFFECTED_ALL: ${{ contains(github.event.pull_request.labels.*.name, 'nx-affected-all') }} @@ -30,19 +29,18 @@ jobs: AFFECTED_ALL: ${{ secrets.AFFECTED_ALL }} CHUNK_SIZE: 7 SERVERSIDE_FEATURES_ON: '' + DOCKER_REGISTRY: 821090935708.dkr.ecr.eu-west-1.amazonaws.com/ + DOCKER_BASE_IMAGE_REGISTRY: 821090935708.dkr.ecr.eu-west-1.amazonaws.com/ecr-public outputs: TEST_CHUNKS: ${{ steps.test_projects.outputs.CHUNKS }} E2E_CHUNKS: ${{ steps.e2e_projects.outputs.CHUNKS }} E2E_BUILD_ID: ${{ steps.e2e_projects.outputs.BUILD_ID }} LINT_CHUNKS: ${{ steps.lint_projects.outputs.CHUNKS }} - UNAFFECTED: ${{ steps.unaffected.outputs.UNAFFECTED }} BUILD_CHUNKS: ${{ steps.build_projects.outputs.CHUNKS }} - BUILD_MAP: ${{ steps.build_map.outputs.BUILD_MAP }} - node-modules-hash: ${{ steps.calculate_node_modules_hash.outputs.node-modules-hash }} - generated-files-cache-key: ${{ steps.calculate_generated_files_cache_key.outputs.generated-files-cache-key }} + CACHE_KEYS: ${{ steps.get-cache.outputs.keys }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-node@v4 @@ -56,7 +54,6 @@ jobs: run: | node -v ls -l `which node` - - name: Checking out relevant branches run: | git checkout $GITHUB_HEAD_REF @@ -76,36 +73,12 @@ jobs: name: pr-event path: event.json retention-days: 60 - - - name: Calculate cache key for node modules - id: calculate_node_modules_hash - run: | - HASH="$(./scripts/ci/get-node-modules-hash.mjs)" - echo "node-modules-hash: ${HASH}" - echo "node-modules-hash=${HASH}" >> $GITHUB_OUTPUT - - - name: Calculate cache keys for generated files - id: calculate_generated_files_cache_key - run: | - export HASH=$(./scripts/_hash-generated-files.sh) - export GENERATED_FILES_KEY=${{ runner.os }}-$HASH-files-generated-03 - echo "GENERATED_FILES_KEY: $GENERATED_FILES_KEY" - echo "generated-files-cache-key=$GENERATED_FILES_KEY" >> $GITHUB_OUTPUT - - - name: Cache for NodeJS dependencies - host OS - id: node-modules - continue-on-error: true - uses: ./.github/actions/cache + - name: Get cache + id: get-cache + uses: ./.github/actions/get-cache with: - path: node_modules - key: ${{ steps.calculate_node_modules_hash.outputs.node-modules-hash }}-yarn - - - name: Check node-modules cache success - run: '[[ "${{ steps.node-modules.outputs.success }}" != "false" ]] || exit 1' - - - name: Building NodeJS dependencies - if: steps.node-modules.outputs.cache-hit != 'true' - run: ./scripts/ci/10_prepare-host-deps.sh + github-token: ${{ secrets.GITHUB_TOKEN }} + enable-cache: 'node_modules,cypress,generated-files' - run: | echo "HEAD=$GITHUB_SHA" >> $GITHUB_ENV @@ -123,47 +96,6 @@ jobs: ISSUE_REPORTING_SLACK_WEBHOOK_URL: ${{ secrets.SLACK_BUILD_ISSUES_REPORTING_WEBHOOK_URL }} name: Preparing HEAD and BASE tags - - name: Cache for cypress - id: cypress - continue-on-error: true - uses: ./.github/actions/cache - with: - path: /github/home/.cache/Cypress - key: cypress-cache-${{ steps.calculate_node_modules_hash.outputs.node-modules-hash }} - - - name: Check cypress cache success - run: '[[ "${{ steps.cypress.outputs.success }}" != "false" ]] || exit 1' - - - name: Verify cypress - id: cypress-check - run: npx cypress verify - continue-on-error: true - - - name: Install cypress - if: steps.cypress-check.outcome != 'success' - run: npx cypress install - - - name: Cache for generated files - id: generated-files-cache - continue-on-error: true - uses: ./.github/actions/cache - with: - path: generated_files.tar.gz - key: ${{ steps.calculate_generated_files_cache_key.outputs.generated-files-cache-key }} - # force-cache-save: ${{ env.SKIP_GENERATED_CACHE }} - - - name: Check generated files cache success - run: '[[ "${{ steps.generated-files-cache.outputs.success }}" != "false" ]] || exit 1' - - - name: Run codegen - if: steps.generated-files-cache.outputs.cache-hit != 'true' - run: | - node --version - tar zcvf generated_files.tar.gz $(./scripts/ci/get-files-touched-by.sh yarn codegen --skip-cache | xargs realpath --relative-to $(pwd)) - - # - name: Security audit Node modules - # run: ./scripts/ci/20_security-audit.sh - - name: License audit Node modules run: ./scripts/ci/20_license-audit.sh @@ -240,7 +172,7 @@ jobs: fail-fast: false matrix: ${{ fromJson(needs.prepare.outputs.TEST_CHUNKS) }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 @@ -251,30 +183,13 @@ jobs: - name: Setup yarn run: npm install -g yarn - - name: Cache for NodeJS dependencies - host OS - id: node-modules - continue-on-error: true - uses: ./.github/actions/cache + - name: Get cache + id: get-cache + uses: ./.github/actions/get-cache with: - path: node_modules - key: ${{ needs.prepare.outputs.node-modules-hash }}-yarn - - - name: Check node-modules cache success - run: '[[ "${{ steps.node-modules.outputs.success }}" != "false" ]] || exit 1' - - - name: Cache for generated files - id: generated-files-cache - continue-on-error: true - uses: ./.github/actions/cache - with: - path: generated_files.tar.gz - key: ${{ needs.prepare.outputs.generated-files-cache-key }} - - - name: Check generated-files cache success - run: '[[ "${{ steps.generated-files-cache.outputs.success }}" != "false" ]] || exit 1' - - - name: Untar generated files - run: tar zxvf generated_files.tar.gz + github-token: ${{ secrets.GITHUB_TOKEN }} + keys: ${{ needs.prepare.outputs.CACHE_KEYS }} + enable-cache: 'node_modules,cypress,generated-files' - uses: ./.github/actions/unit-test with: @@ -303,7 +218,7 @@ jobs: fail-fast: false matrix: ${{ fromJson(needs.prepare.outputs.E2E_CHUNKS) }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: @@ -312,41 +227,13 @@ jobs: - name: Setup yarn run: npm install -g yarn - - name: Cache for NodeJS dependencies - host OS - id: node-modules - continue-on-error: true - uses: ./.github/actions/cache - with: - path: node_modules - key: ${{ needs.prepare.outputs.node-modules-hash }}-yarn - - - name: Check node-modules cache success - run: '[[ "${{ steps.node-modules.outputs.success }}" != "false" ]] || exit 1' - - - name: Cache for cypress - id: cypress - continue-on-error: true - uses: ./.github/actions/cache - with: - path: /github/home/.cache/Cypress - key: cypress-cache-${{ needs.prepare.outputs.node-modules-hash }} - - - name: Check cypress cache success - run: '[[ "${{ steps.cypress.outputs.success }}" != "false" ]] || exit 1' - - - name: Cache for generated files - id: generated-files-cache - continue-on-error: true - uses: ./.github/actions/cache + - name: Get cache + id: get-cache + uses: ./.github/actions/get-cache with: - path: generated_files.tar.gz - key: ${{ needs.prepare.outputs.generated-files-cache-key }} - - - name: Check generated-files cache success - run: '[[ "${{ steps.generated-files-cache.outputs.success }}" != "false" ]] || exit 1' - - - name: Untar generated files - run: tar zxvf generated_files.tar.gz + github-token: ${{ secrets.GITHUB_TOKEN }} + keys: ${{ needs.prepare.outputs.CACHE_KEYS }} + enable-cache: 'node_modules,cypress,generated-files' - name: Running e2e tests run: ./scripts/ci/40_e2e.sh ${AFFECTED_PROJECT} @@ -359,23 +246,20 @@ jobs: image: public.ecr.aws/m3u4c4h9/island-is/actions-runner-public:latest timeout-minutes: 5 steps: - - uses: actions/checkout@v3 - - name: Cache for NodeJS dependencies - host OS - id: node-modules - continue-on-error: true - uses: ./.github/actions/cache - with: - path: node_modules - key: ${{ needs.prepare.outputs.node-modules-hash }}-yarn - + - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version-file: 'package.json' - - name: Setup yarn run: npm install -g yarn - - name: Check node-modules cache success - run: '[[ "${{ steps.node-modules.outputs.success }}" != "false" ]] || exit 1' + - name: Get cache + id: get-cache + uses: ./.github/actions/get-cache + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + keys: ${{ needs.prepare.outputs.CACHE_KEYS }} + enable-cache: 'node_modules,generated-files' + - name: Linting workspace run: ./scripts/ci/20_lint-workspace.sh @@ -385,7 +269,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Run ShellCheck uses: ludeeus/action-shellcheck@2.0.0 with: @@ -402,13 +286,13 @@ jobs: image: public.ecr.aws/m3u4c4h9/island-is/actions-runner-public:latest timeout-minutes: 5 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: ${{ github.event_name == 'pull_request' }} with: token: ${{ secrets.DIRTY_FIX_BOT_TOKEN }} ref: ${{ github.event.pull_request.head.ref }} - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: ${{ github.ref == 'ref/heads/main' }} - uses: actions/setup-node@v4 @@ -417,16 +301,13 @@ jobs: - name: Setup yarn run: npm install -g yarn - - name: Cache for NodeJS dependencies - host OS - id: node-modules - continue-on-error: true - uses: ./.github/actions/cache + - name: Get cache + id: get-cache + uses: ./.github/actions/get-cache with: - path: node_modules - key: ${{ needs.prepare.outputs.node-modules-hash }}-yarn - - - name: Check node-modules cache success - run: '[[ "${{ steps.node-modules.outputs.success }}" != "false" ]] || exit 1' + github-token: ${{ secrets.GITHUB_TOKEN }} + keys: ${{ needs.prepare.outputs.CACHE_KEYS }} + enable-cache: 'node_modules' - name: NX format:check if: ${{ github.ref == 'ref/heads/main' }} @@ -454,37 +335,19 @@ jobs: fail-fast: false matrix: ${{ fromJson(needs.prepare.outputs.LINT_CHUNKS) }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version-file: 'package.json' - name: Setup yarn run: npm install -g yarn - - name: Cache for NodeJS dependencies - host OS - id: node-modules - continue-on-error: true - uses: ./.github/actions/cache - with: - path: node_modules - key: ${{ needs.prepare.outputs.node-modules-hash }}-yarn - - - name: Check node-modules cache success - run: '[[ "${{ steps.node-modules.outputs.success }}" != "false" ]] || exit 1' - - - name: Cache for generated files - id: generated-files-cache - continue-on-error: true - uses: ./.github/actions/cache + - name: Get cache + id: get-cache + uses: ./.github/actions/get-cache with: - path: generated_files.tar.gz - key: ${{ needs.prepare.outputs.generated-files-cache-key }} - - - name: Check generated-files cache success - run: '[[ "${{ steps.generated-files-cache.outputs.success }}" != "false" ]] || exit 1' - - - name: Untar generated files - run: tar zxvf generated_files.tar.gz - + github-token: ${{ secrets.GITHUB_TOKEN }} + keys: ${{ needs.prepare.outputs.CACHE_KEYS }} + enable-cache: 'node_modules,generated-files' - name: Linting run: ./scripts/ci/run-in-parallel-native.sh lint @@ -503,36 +366,19 @@ jobs: matrix: ${{ fromJson(needs.prepare.outputs.BUILD_CHUNKS) }} if: needs.prepare.outputs.BUILD_CHUNKS steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version-file: 'package.json' - name: Setup yarn run: npm install -g yarn - - name: Cache for NodeJS dependencies - host OS - id: node-modules - continue-on-error: true - uses: ./.github/actions/cache - with: - path: node_modules - key: ${{ needs.prepare.outputs.node-modules-hash }}-yarn - - - name: Check node-modules cache success - run: '[[ "${{ steps.node-modules.outputs.success }}" != "false" ]] || exit 1' - - - name: Cache for generated files - id: generated-files-cache - continue-on-error: true - uses: ./.github/actions/cache + - name: Get cache + id: get-cache + uses: ./.github/actions/get-cache with: - path: generated_files.tar.gz - key: ${{ needs.prepare.outputs.generated-files-cache-key }} - - - name: Check generated-files cache success - run: '[[ "${{ steps.generated-files-cache.outputs.success }}" != "false" ]] || exit 1' - - - name: Untar generated files - run: tar zxvf generated_files.tar.gz + github-token: ${{ secrets.GITHUB_TOKEN }} + keys: ${{ needs.prepare.outputs.CACHE_KEYS }} + enable-cache: 'node_modules,generated-files' - name: Building run: ./scripts/ci/run-in-parallel-native.sh build diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index ed33617bba7a..3ea89d74cc04 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -156,6 +156,7 @@ jobs: SERVERSIDE_FEATURES_ON: '' outputs: + CACHE_KEYS: ${{ steps.get-cache.outputs.keys }} TEST_CHUNKS: ${{ steps.test_projects.outputs.CHUNKS }} DOCKER_TAG: ${{ steps.docker_tags.outputs.DOCKER_TAG }} NODE_IMAGE_TAG: ${{ steps.nodejs_image.outputs.NODE_IMAGE_TAG }} @@ -164,7 +165,6 @@ jobs: BUILD_CHUNKS: ${{ steps.build_map.outputs.BUILD_CHUNKS }} IMAGES: ${{ steps.deploy_map.outputs.IMAGES }} node-modules-hash: ${{ steps.calculate_node_modules_hash.outputs.node-modules-hash }} - generated-files-cache-key: ${{ steps.calculate_generated_files_cache_key.outputs.generated-files-cache-key }} steps: - uses: actions/checkout@v3 with: @@ -284,35 +284,12 @@ jobs: id: git_nx_head name: Preparing HEAD tag - - name: Calculate cache key for node modules - id: calculate_node_modules_hash - run: | - HASH="$(./scripts/ci/get-node-modules-hash.mjs)" - echo "node-modules-hash: ${HASH}" - echo "node-modules-hash=${HASH}" >> $GITHUB_OUTPUT - - - name: Calculate cache keys for generated files - id: calculate_generated_files_cache_key - run: | - export HASH=$(./scripts/_hash-generated-files.sh) - export GENERATED_FILES_KEY=${{ runner.os }}-$HASH-files-generated - echo "GENERATED_FILES_KEY: $GENERATED_FILES_KEY" - echo "generated-files-cache-key=$GENERATED_FILES_KEY" >> $GITHUB_OUTPUT - - - name: Cache for NodeJS dependencies - host OS - id: node-modules - continue-on-error: true - uses: ./.github/actions/cache + - name: Get cache + id: get-cache + uses: ./.github/actions/get-cache with: - path: node_modules - key: ${{ steps.calculate_node_modules_hash.outputs.node-modules-hash }}-yarn - - - name: Check cache success - run: '[[ "${{ steps.node-modules.outputs.success }}" != "false" ]] || exit 1' - - - name: Building NodeJS dependencies - if: steps.node-modules.outputs.cache-hit != 'true' - run: ./scripts/ci/10_prepare-host-deps.sh + github-token: ${{ secrets.GITHUB_TOKEN }} + enable-cache: 'node_modules,generated-files' - name: Set Test Everything true run: | @@ -357,7 +334,15 @@ jobs: image: '${{ env.DOCKER_BASE_IMAGE_REGISTRY }}/eks-distro-build-tooling/binfmt-misc:qemu-v6.1.0' - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 - + - name: Get node hash + uses: actions/github-script@v7 + id: calculate_node_modules_hash + env: + _CACHE_KEY: ${{ steps.get-cache.outputs.keys }} + with: + script: | + const CACHE_KEYS = JSON.parse(process.env._CACHE_KEY) + core.setOutput('node-modules-hash', CACHE_KEYS["node_modules"]) - name: Cache for NodeJS dependencies - Docker layer id: cache-deps continue-on-error: true @@ -380,31 +365,9 @@ jobs: - name: Check cache success run: '[[ "${{ steps.cache-deps-base.outputs.success }}" != "false" ]] || exit 1' - - name: Building NodeJS dependencies - if: steps.cache-deps.outputs.cache-hit != 'true' || steps.cache-deps-base.outputs.cache-hit != 'true' - run: | - ./scripts/ci/10_prepare-docker-deps.sh - - name: set BRANCH env var run: echo "BRANCH=$GIT_BRANCH" >> $GITHUB_ENV - - name: Cache for generated files - id: generated-files-cache - continue-on-error: true - uses: ./.github/actions/cache - with: - path: generated_files.tar.gz - key: ${{ steps.calculate_generated_files_cache_key.outputs.generated-files-cache-key }} - - - name: Check cache success - run: '[[ "${{ steps.generated-files-cache.outputs.success }}" != "false" ]] || exit 1' - - - name: Run codegen - if: steps.generated-files-cache.outputs.cache-hit != 'true' || env.SKIP_GENERATED_CACHE == 'true' - run: | - node --version - tar zcvf generated_files.tar.gz $(./scripts/ci/get-files-touched-by.sh yarn codegen --skip-cache | xargs realpath --relative-to $(pwd)) - - name: Prepare test targets id: test_projects run: | @@ -472,30 +435,13 @@ jobs: - name: Setup yarn run: npm install -g yarn - - name: Cache for NodeJS dependencies - host OS - id: node-modules - continue-on-error: true - uses: ./.github/actions/cache - with: - path: node_modules - key: ${{ needs.prepare.outputs.node-modules-hash }}-yarn - - - name: Check cache success - run: '[[ "${{ steps.node-modules.outputs.success }}" != "false" ]] || exit 1' - - - name: Cache for generated files - id: generated-files-cache - continue-on-error: true - uses: ./.github/actions/cache + - name: Get cache + id: get-cache + uses: ./.github/actions/get-cache with: - path: generated_files.tar.gz - key: ${{ needs.prepare.outputs.generated-files-cache-key }} - - - name: Check cache success - run: '[[ "${{ steps.generated-files-cache.outputs.success }}" != "false" ]] || exit 1' - - - name: Untar generated files - run: tar zxvf generated_files.tar.gz + github-token: ${{ secrets.GITHUB_TOKEN }} + keys: ${{ needs.prepare.outputs.CACHE_KEYS }} + enable-cache: 'node_modules,generated-files' - uses: ./.github/actions/unit-test with: @@ -544,21 +490,13 @@ jobs: run: npm install -g yarn if: steps.gather.outcome == 'success' - - name: Cache for generated files - id: generated-files-cache - if: steps.gather.outcome == 'success' - continue-on-error: true - uses: ./.github/actions/cache + - name: Get cache + id: get-cache + uses: ./.github/actions/get-cache with: - path: generated_files.tar.gz - key: ${{ needs.prepare.outputs.generated-files-cache-key }} - - - name: Check cache success - run: '[[ "${{ steps.generated-files-cache.outputs.success }}" != "false" ]] || exit 1' - - - name: Untar generated files - if: steps.gather.outcome == 'success' - run: tar zxvf generated_files.tar.gz + github-token: ${{ secrets.GITHUB_TOKEN }} + keys: ${{ needs.prepare.outputs.CACHE_KEYS }} + enable-cache: 'node_modules,generated-files' - name: Cache for dependencies Docker layer if: steps.gather.outcome == 'success' @@ -584,18 +522,6 @@ jobs: - name: Check cache success run: '[[ "${{ steps.cache-deps.outputs.success }}" != "false" ]] || exit 1' - # needed to run `nx show project` in scripts/ci/_docker.sh - should be refactored. - - name: Cache for NodeJS dependencies - host OS - id: node-modules - continue-on-error: true - uses: ./.github/actions/cache - with: - path: node_modules - key: ${{ needs.prepare.outputs.node-modules-hash }}-yarn - - - name: Check cache success - run: '[[ "${{ steps.node-modules.outputs.success }}" != "false" ]] || exit 1' - - name: Docker login to ECR repo if: steps.gather.outcome == 'success' run: ./scripts/ci/docker-login-ecr.sh diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index 3fbf0f5b2c94..42f08b552804 100644 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -185,6 +185,9 @@ import { import { HousingBenefitsConfig } from '@island.is/clients/hms-housing-benefits' import { UserProfileClientConfig } from '@island.is/clients/user-profile' import { UltravioletRadiationClientConfig } from '@island.is/clients/ultraviolet-radiation' +import { CriminalRecordClientConfig } from '@island.is/clients/criminal-record' +import { HealthInsuranceV2ClientConfig } from '@island.is/clients/icelandic-health-insurance/health-insurance' +import { VmstClientConfig } from '@island.is/clients/vmst' const environment = getConfig @@ -223,7 +226,7 @@ const environment = getConfig baseApiUrl: environment.applicationSystem.baseApiUrl!, }), LicenseServiceModule, - DirectorateOfLabourModule.register(), + DirectorateOfLabourModule, FileUploadModule, DocumentModule, DocumentProviderModule.register({ @@ -246,15 +249,7 @@ const environment = getConfig }), CmsTranslationsModule, TerminusModule, - HealthInsuranceModule.register({ - clientV2Config: { - xRoadBaseUrl: environment.healthInsuranceV2.xRoadBaseUrl!, - xRoadProviderId: environment.healthInsuranceV2.xRoadProviderId!, - xRoadClientId: environment.healthInsuranceV2.xRoadClientId!, - username: environment.healthInsuranceV2.username!, - password: environment.healthInsuranceV2.password!, - }, - }), + HealthInsuranceModule, UserProfileModule.register({ islykill: { cert: environment.islykill.cert!, @@ -309,13 +304,7 @@ const environment = getConfig ApiDomainsPaymentModule, PaymentScheduleModule, ProblemModule, - CriminalRecordModule.register({ - clientConfig: { - xroadBaseUrl: environment.xroad.baseUrl!, - xroadClientId: environment.xroad.clientId!, - xroadPath: environment.criminalRecord.xroadPath!, - }, - }), + CriminalRecordModule, MunicipalitiesFinancialAidModule, FishingLicenseModule, MortgageCertificateModule, @@ -414,6 +403,9 @@ const environment = getConfig LicenseConfig, UserProfileClientConfig, UltravioletRadiationClientConfig, + VmstClientConfig, + HealthInsuranceV2ClientConfig, + CriminalRecordClientConfig, ], }), ], diff --git a/apps/api/src/app/environments/environment.ts b/apps/api/src/app/environments/environment.ts index dcf935a98db5..588fbe19ccfe 100644 --- a/apps/api/src/app/environments/environment.ts +++ b/apps/api/src/app/environments/environment.ts @@ -23,9 +23,6 @@ const prodConfig = () => ({ : process.env.XROAD_DRIVING_LICENSE_V2_PATH, }, }, - criminalRecord: { - xroadPath: process.env.XROAD_CRIMINAL_RECORD_PATH, - }, education: { xroadLicenseServiceId: process.env.XROAD_MMS_LICENSE_SERVICE_ID, xroadGradeServiceId: process.env.XROAD_MMS_GRADE_SERVICE_ID, @@ -42,13 +39,6 @@ const prodConfig = () => ({ clientID: process.env.XROAD_CLIENT_ID, xroadID: process.env.XROAD_HEALTH_INSURANCE_ID, }, - healthInsuranceV2: { - xRoadBaseUrl: process.env.XROAD_BASE_PATH, - xRoadClientId: process.env.XROAD_CLIENT_ID, - xRoadProviderId: process.env.XROAD_HEALTH_INSURANCE_ID, - username: process.env.XROAD_HEALTH_INSURANCE_V2_XROAD_USERNAME, - password: process.env.XROAD_HEALTH_INSURANCE_V2_XROAD_PASSWORD, - }, auth: { issuer: process.env.IDENTITY_SERVER_ISSUER_URL, audience: ['@island.is', '@admin.island.is'], @@ -129,11 +119,6 @@ const devConfig = () => ({ 'r1/IS-DEV/GOV/10005/Logreglan-Protected/RafraentOkuskirteini-v2', }, }, - criminalRecord: { - xroadPath: - process.env.XROAD_CRIMINAL_RECORD_PATH ?? - 'r1/IS-DEV/GOV/10005/Logreglan-Protected/Sakavottord-PDF-v2', - }, education: { xroadLicenseServiceId: 'IS-DEV/GOV/10066/MMS-Protected/license-api-v1', xroadGradeServiceId: 'IS-DEV/GOV/10066/MMS-Protected/grade-api-v1', @@ -152,16 +137,6 @@ const devConfig = () => ({ clientID: process.env.XROAD_CLIENT_ID ?? '', xroadID: process.env.XROAD_HEALTH_INSURANCE_ID ?? '', }, - healthInsuranceV2: { - xRoadBaseUrl: process.env.XROAD_BASE_PATH ?? 'http://localhost:8080', - xRoadClientId: - process.env.XROAD_CLIENT_ID ?? 'IS-DEV/GOV/10000/island-is-client', - xRoadProviderId: - process.env.XROAD_HEALTH_INSURANCE_ID ?? - 'IS-DEV/GOV/10007/SJUKRA-Protected', - username: process.env.XROAD_HEALTH_INSURANCE_V2_XROAD_USERNAME ?? '', - password: process.env.XROAD_HEALTH_INSURANCE_V2_XROAD_PASSWORD ?? '', - }, auth: { issuer: 'https://identity-server.dev01.devland.is', audience: ['@island.is', '@admin.island.is'], diff --git a/apps/application-system/api/src/app/app.module.ts b/apps/application-system/api/src/app/app.module.ts index d81eb4cafd50..f410c2afc1cc 100644 --- a/apps/application-system/api/src/app/app.module.ts +++ b/apps/application-system/api/src/app/app.module.ts @@ -38,6 +38,10 @@ import { SignatureCollectionClientConfig } from '@island.is/clients/signature-co import { InnaClientConfig } from '@island.is/clients/inna' import { OfficialJournalOfIcelandClientConfig } from '@island.is/clients/official-journal-of-iceland' import { OfficialJournalOfIcelandApplicationClientConfig } from '@island.is/clients/official-journal-of-iceland/application' +import { DataProtectionComplaintClientConfig } from '@island.is/clients/data-protection-complaint' +import { CriminalRecordClientConfig } from '@island.is/clients/criminal-record' +import { HealthInsuranceV2ClientConfig } from '@island.is/clients/icelandic-health-insurance/health-insurance' +import { VmstClientConfig } from '@island.is/clients/vmst' @Module({ imports: [ @@ -79,6 +83,10 @@ import { OfficialJournalOfIcelandApplicationClientConfig } from '@island.is/clie InnaClientConfig, OfficialJournalOfIcelandClientConfig, OfficialJournalOfIcelandApplicationClientConfig, + DataProtectionComplaintClientConfig, + CriminalRecordClientConfig, + HealthInsuranceV2ClientConfig, + VmstClientConfig, ], }), ], diff --git a/apps/application-system/api/src/environments/environment.ts b/apps/application-system/api/src/environments/environment.ts index 6de3ad51ee9a..ebf09a8ed504 100644 --- a/apps/application-system/api/src/environments/environment.ts +++ b/apps/application-system/api/src/environments/environment.ts @@ -37,38 +37,11 @@ const devConfig = { password: process.env.NOVA_PASSWORD, acceptUnauthorized: true, }, - criminalRecord: { - clientConfig: { - xroadClientId: - process.env.XROAD_CLIENT_ID ?? 'IS-DEV/GOV/10000/island-is-client', - xroadBaseUrl: process.env.XROAD_BASE_PATH ?? 'http://localhost:8081', - xroadPath: - process.env.XROAD_CRIMINAL_RECORD_PATH ?? - 'r1/IS-DEV/GOV/10005/Logreglan-Protected/Sakavottord-PDF-v2', - }, - }, presignBucket: process.env.FILE_SERVICE_PRESIGN_BUCKET, attachmentBucket: process.env.APPLICATION_ATTACHMENT_BUCKET, generalPetition: { endorsementsApiBasePath: 'http://localhost:4246', }, - healthInsuranceV2: { - xRoadBaseUrl: process.env.XROAD_BASE_PATH ?? 'http://localhost:8080', - xRoadProviderId: - process.env.XROAD_HEALTH_INSURANCE_ID ?? - 'IS-DEV/GOV/10007/SJUKRA-Protected', - xRoadClientId: - process.env.XROAD_CLIENT_ID ?? 'IS-DEV/GOV/10000/island-is-client', - username: process.env.XROAD_HEALTH_INSURANCE_V2_XROAD_USERNAME ?? '', - password: process.env.XROAD_HEALTH_INSURANCE_V2_XROAD_PASSWORD ?? '', - }, - dataProtectionComplaint: { - password: process.env.DATA_PROTECTION_COMPLAINT_API_PASSWORD, - username: process.env.DATA_PROTECTION_COMPLAINT_API_USERNAME, - XRoadProviderId: process.env.DATA_PROTECTION_COMPLAINT_XROAD_PROVIDER_ID, - xRoadClientId: process.env.XROAD_CLIENT_ID, - xRoadBaseUrl: process.env.XROAD_BASE_PATH ?? 'http://localhost:8080', - }, userProfile: { serviceBasePath: 'http://localhost:3366', }, @@ -123,30 +96,9 @@ const prodConfig = { }, presignBucket: process.env.FILE_SERVICE_PRESIGN_BUCKET, attachmentBucket: process.env.APPLICATION_ATTACHMENT_BUCKET, - criminalRecord: { - clientConfig: { - xroadClientId: process.env.XROAD_CLIENT_ID, - xroadBaseUrl: process.env.XROAD_BASE_PATH, - xroadPath: process.env.XROAD_CRIMINAL_RECORD_PATH, - }, - }, generalPetition: { endorsementsApiBasePath: process.env.ENDORSEMENTS_API_BASE_PATH, }, - healthInsuranceV2: { - xRoadBaseUrl: process.env.XROAD_BASE_PATH, - xRoadProviderId: process.env.XROAD_HEALTH_INSURANCE_ID, - xRoadClientId: process.env.XROAD_CLIENT_ID, - username: process.env.XROAD_HEALTH_INSURANCE_V2_XROAD_USERNAME, - password: process.env.XROAD_HEALTH_INSURANCE_V2_XROAD_PASSWORD, - }, - dataProtectionComplaint: { - password: process.env.DATA_PROTECTION_COMPLAINT_API_PASSWORD, - username: process.env.DATA_PROTECTION_COMPLAINT_API_USERNAME, - XRoadProviderId: process.env.DATA_PROTECTION_COMPLAINT_XROAD_PROVIDER_ID, - xRoadClientId: process.env.XROAD_CLIENT_ID, - xRoadBaseUrl: process.env.XROAD_BASE_PATH, - }, userProfile: { serviceBasePath: process.env.SERVICE_USER_PROFILE_URL, }, diff --git a/apps/consultation-portal/components/Layout/Layout.json b/apps/consultation-portal/components/Layout/Layout.json index b49cfe4a6045..963248ac7337 100644 --- a/apps/consultation-portal/components/Layout/Layout.json +++ b/apps/consultation-portal/components/Layout/Layout.json @@ -16,6 +16,10 @@ "label": "Áskriftir", "href": "/askriftir" }, + { + "label": "Tölfræði", + "href": "/tolfraedi" + }, { "label": "Mínar umsagnir", "href": "/umsagnir" diff --git a/apps/consultation-portal/components/Layout/components/Menu/components/MenuItems/MenuItems.ts b/apps/consultation-portal/components/Layout/components/Menu/components/MenuItems/MenuItems.ts index 9b2b6c77bcfc..04db232a20fb 100644 --- a/apps/consultation-portal/components/Layout/components/Menu/components/MenuItems/MenuItems.ts +++ b/apps/consultation-portal/components/Layout/components/Menu/components/MenuItems/MenuItems.ts @@ -13,14 +13,14 @@ export const menuItems = [ href: loc[1].href, testId: 'subscriptions-btn', }, - // Tölfræði is hidden until PowerBI - // { - // label: 'Tölfræði', - // href: '/tolfraedi', - // }, { label: loc[2].label, href: loc[2].href, + testId: 'statistics-btn', + }, + { + label: loc[3].label, + href: loc[3].href, testId: 'advices-btn', }, ] diff --git a/apps/consultation-portal/components/PowerBI/PowerBI.tsx b/apps/consultation-portal/components/PowerBI/PowerBI.tsx new file mode 100644 index 000000000000..7abb922eada5 --- /dev/null +++ b/apps/consultation-portal/components/PowerBI/PowerBI.tsx @@ -0,0 +1,31 @@ +import { useRef } from 'react' +import { PowerBIEmbed } from 'powerbi-client-react' +import { type Embed, models } from 'powerbi-client' + +export const PowerBI = () => { + const embedRef = useRef(null) + + const getEmbeddedComponent = (embed: Embed) => { + embed.element.style.height = '700px' + embed.element.style.width = '100%' + embed.iframe.style.border = 'none' + embedRef.current = embed + } + + return ( + + ) +} + +export default PowerBI diff --git a/apps/consultation-portal/components/PowerBI/index.ts b/apps/consultation-portal/components/PowerBI/index.ts new file mode 100644 index 000000000000..a8748281fdc6 --- /dev/null +++ b/apps/consultation-portal/components/PowerBI/index.ts @@ -0,0 +1,5 @@ +import dynamic from 'next/dynamic' + +export const PowerBIComponent = dynamic(() => import('./PowerBI'), { + ssr: false, +}) diff --git a/apps/consultation-portal/pages/tolfraedi.tsx b/apps/consultation-portal/pages/tolfraedi.tsx deleted file mode 100644 index 9bb43ab068c4..000000000000 --- a/apps/consultation-portal/pages/tolfraedi.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Layout } from '../components' -import { Box, GridColumn, GridRow } from '@island.is/island-ui/core' -const Statistics = () => { - return ( - - - - - {/* */} - - - - - ) -} - -export default Statistics diff --git a/apps/consultation-portal/pages/tolfraedi/index.tsx b/apps/consultation-portal/pages/tolfraedi/index.tsx new file mode 100644 index 000000000000..1ec2bae64598 --- /dev/null +++ b/apps/consultation-portal/pages/tolfraedi/index.tsx @@ -0,0 +1,5 @@ +import { StatisticsScreen } from '../../screens/Statistics/Statistics' + +export const Index = () => + +export default Index diff --git a/apps/consultation-portal/pages/user/link-expired/index.tsx b/apps/consultation-portal/pages/user/link-expired/index.tsx new file mode 100644 index 000000000000..43b9a6aeba0d --- /dev/null +++ b/apps/consultation-portal/pages/user/link-expired/index.tsx @@ -0,0 +1,7 @@ +import LinkExpiredScreen from '../../../screens/EmailVerified/LinkExpired' + +export const Index = () => { + return +} + +export default Index diff --git a/apps/consultation-portal/pages/user/not-found/index.tsx b/apps/consultation-portal/pages/user/not-found/index.tsx new file mode 100644 index 000000000000..e8fb87a89a4a --- /dev/null +++ b/apps/consultation-portal/pages/user/not-found/index.tsx @@ -0,0 +1,7 @@ +import NotFoundScreen from '../../../screens/EmailVerified/NotFound' + +export const Index = () => { + return +} + +export default Index diff --git a/apps/consultation-portal/screens/EmailVerified/EmailVerified.json b/apps/consultation-portal/screens/EmailVerified/EmailVerified.json index bec3b37792d6..d1525f11c973 100644 --- a/apps/consultation-portal/screens/EmailVerified/EmailVerified.json +++ b/apps/consultation-portal/screens/EmailVerified/EmailVerified.json @@ -8,5 +8,25 @@ "emailVerifiedText": "Netfang staðfest", "text": "Nú geturðu skráð þig í áskrift að málum, hægt er að velja um nokkrar leiðir.", "arrowLinkText": "Aftur á forsíðu" + }, + "linkExpired": { + "seo": { + "title": "Hlekkur útrunninn", + "url": "/user/link-expired" + }, + "failText": "Aðgerð tókst ekki", + "linkExpiredText": "Hlekkur útrunninn", + "text": "Hlekkur er útrunninn, vinsamlegast athugaðu að hlekkur sé ekki eldri en 24 klukkustundir. Vinsamlegast sláðu inn netfang aftur og fáðu nýjan hlekk.", + "arrowLinkText": "Aftur á forsíðu" + }, + "notFound": { + "seo": { + "title": "Eitthvað fór úrskeiðis", + "url": "/user/not-found" + }, + "failText": "Aðgerð tókst ekki", + "notFoundText": "Eitthvað fór úrskeiðis", + "text": "Reyndu aftur eða hafðu samband.", + "arrowLinkText": "Aftur á forsíðu" } } diff --git a/apps/consultation-portal/screens/EmailVerified/LinkExpired.tsx b/apps/consultation-portal/screens/EmailVerified/LinkExpired.tsx new file mode 100644 index 000000000000..f1033d5ee92d --- /dev/null +++ b/apps/consultation-portal/screens/EmailVerified/LinkExpired.tsx @@ -0,0 +1,46 @@ +import { + ArrowLink, + Box, + GridColumn, + GridContainer, + GridRow, + Text, +} from '@island.is/island-ui/core' +import { Layout } from '../../components' +import localization from './EmailVerified.json' + +export const LinkExpiredScreen = () => { + const loc = localization['linkExpired'] + return ( + + + + + + + {loc.failText} + + + + {loc.linkExpiredText} + + {loc.text} + {loc.arrowLinkText} + + + + + + ) +} +export default LinkExpiredScreen diff --git a/apps/consultation-portal/screens/EmailVerified/NotFound.tsx b/apps/consultation-portal/screens/EmailVerified/NotFound.tsx new file mode 100644 index 000000000000..64682a2edb1b --- /dev/null +++ b/apps/consultation-portal/screens/EmailVerified/NotFound.tsx @@ -0,0 +1,46 @@ +import { + ArrowLink, + Box, + GridColumn, + GridContainer, + GridRow, + Text, +} from '@island.is/island-ui/core' +import { Layout } from '../../components' +import localization from './EmailVerified.json' + +export const NotFoundScreen = () => { + const loc = localization['notFound'] + return ( + + + + + + + {loc.failText} + + + + {loc.notFoundText} + + {loc.text} + {loc.arrowLinkText} + + + + + + ) +} +export default NotFoundScreen diff --git a/apps/consultation-portal/screens/Statistics/Statistics.json b/apps/consultation-portal/screens/Statistics/Statistics.json new file mode 100644 index 000000000000..d94712e8309b --- /dev/null +++ b/apps/consultation-portal/screens/Statistics/Statistics.json @@ -0,0 +1,19 @@ +{ + "statistics": { + "seo": { + "title": "Tölfræði", + "url": "/tolfraedi", + "description": "Tölfræði um samráðsgátt", + "keywords": "" + }, + "breadcrumbs": [ + { + "title": "Samráðsgátt", + "href": "/samradsgatt" + }, + { + "title": "Tölfræði" + } + ] + } +} diff --git a/apps/consultation-portal/screens/Statistics/Statistics.tsx b/apps/consultation-portal/screens/Statistics/Statistics.tsx new file mode 100644 index 000000000000..7d8468d9c4bc --- /dev/null +++ b/apps/consultation-portal/screens/Statistics/Statistics.tsx @@ -0,0 +1,32 @@ +import { GridContainer } from '@island.is/island-ui/core' + +import { Breadcrumbs, Layout } from '../../components' +import localization from './Statistics.json' +import { PowerBIComponent } from '../../components/PowerBI' + +export const StatisticsScreen = () => { + const loc = localization['statistics'] + + return ( + + + + + + + ) +} + +export default StatisticsScreen diff --git a/apps/judicial-system/backend/src/app/formatters/pdfHelpers.ts b/apps/judicial-system/backend/src/app/formatters/pdfHelpers.ts index 76047360dc5d..5e4e35bf0a9f 100644 --- a/apps/judicial-system/backend/src/app/formatters/pdfHelpers.ts +++ b/apps/judicial-system/backend/src/app/formatters/pdfHelpers.ts @@ -28,15 +28,16 @@ const setFont = (doc: PDFKit.PDFDocument, font?: string) => { } } -const addCenteredText = ( +const addAlignedText = ( doc: PDFKit.PDFDocument, fontSize: number, heading: string, + alignment: 'center' | 'left' | 'right' | 'justify', font?: string, ) => { setFont(doc, font) - doc.fontSize(fontSize).text(heading, { align: 'center', paragraphGap: 1 }) + doc.fontSize(fontSize).text(heading, { align: alignment, paragraphGap: 1 }) } const addText = ( @@ -51,17 +52,6 @@ const addText = ( doc.fontSize(fontSize).text(text, { continued, paragraphGap: 1 }) } -const addJustifiedText = ( - doc: PDFKit.PDFDocument, - fontSize: number, - text: string, - font?: string, -) => { - setFont(doc, font) - - doc.fontSize(fontSize).text(text, { align: 'justify', paragraphGap: 1 }) -} - export const setTitle = (doc: PDFKit.PDFDocument, title: string) => { if (doc.info) { doc.info['Title'] = title @@ -333,7 +323,7 @@ export const addGiganticHeading = ( heading: string, font?: string, ) => { - addCenteredText(doc, giganticFontSize, heading, font) + addAlignedText(doc, giganticFontSize, heading, 'center', font) } export const addHugeHeading = ( @@ -341,7 +331,7 @@ export const addHugeHeading = ( heading: string, font?: string, ) => { - addCenteredText(doc, hugeFontSize, heading, font) + addAlignedText(doc, hugeFontSize, heading, 'center', font) } export const addLargeHeading = ( @@ -349,7 +339,7 @@ export const addLargeHeading = ( heading: string, font?: string, ) => { - addCenteredText(doc, largeFontSize, heading, font) + addAlignedText(doc, largeFontSize, heading, 'center', font) } export const addMediumPlusHeading = ( @@ -357,7 +347,7 @@ export const addMediumPlusHeading = ( heading: string, font?: string, ) => { - addCenteredText(doc, mediumPlusFontSize, heading, font) + addAlignedText(doc, mediumPlusFontSize, heading, 'center', font) } export const addMediumHeading = ( @@ -365,7 +355,7 @@ export const addMediumHeading = ( heading: string, font?: string, ) => { - addCenteredText(doc, mediumFontSize, heading, font) + addAlignedText(doc, mediumFontSize, heading, 'center', font) } export const addLargeText = ( @@ -398,7 +388,7 @@ export const addNormalPlusCenteredText = ( text: string, font?: string, ) => { - addCenteredText(doc, basePlusFontSize, text, font) + addAlignedText(doc, basePlusFontSize, text, 'center', font) } export const addNormalText = ( @@ -415,7 +405,7 @@ export const addNormalJustifiedText = ( text: string, font?: string, ) => { - addJustifiedText(doc, baseFontSize, text, font) + addAlignedText(doc, baseFontSize, text, 'justify', font) } export const addNormalPlusJustifiedText = ( @@ -423,7 +413,7 @@ export const addNormalPlusJustifiedText = ( text: string, font?: string, ) => { - addJustifiedText(doc, basePlusFontSize, text, font) + addAlignedText(doc, basePlusFontSize, text, 'justify', font) } export const addNormalCenteredText = ( @@ -431,7 +421,15 @@ export const addNormalCenteredText = ( text: string, font?: string, ) => { - addCenteredText(doc, baseFontSize, text, font) + addAlignedText(doc, baseFontSize, text, 'center', font) +} + +export const addNormalRightAlignedText = ( + doc: PDFKit.PDFDocument, + text: string, + font?: string, +) => { + addAlignedText(doc, baseFontSize, text, 'right', font) } export const addNumberedList = ( diff --git a/apps/judicial-system/backend/src/app/formatters/subpoenaPdf.ts b/apps/judicial-system/backend/src/app/formatters/subpoenaPdf.ts new file mode 100644 index 000000000000..7b9fc2da66b9 --- /dev/null +++ b/apps/judicial-system/backend/src/app/formatters/subpoenaPdf.ts @@ -0,0 +1,151 @@ +import PDFDocument from 'pdfkit' + +import { FormatMessage } from '@island.is/cms-translations' + +import { formatDate, formatDOB } from '@island.is/judicial-system/formatters' +import { + DateType, + DistrictCourtLocation, + DistrictCourts, + SubpoenaType, +} from '@island.is/judicial-system/types' + +import { subpoena as strings } from '../messages' +import { Case } from '../modules/case' +import { Defendant } from '../modules/defendant' +import { + addEmptyLines, + addFooter, + addHugeHeading, + addMediumText, + addNormalRightAlignedText, + addNormalText, + setTitle, +} from './pdfHelpers' + +export const createSubpoenaPDF = ( + theCase: Case, + formatMessage: FormatMessage, + defendant?: Defendant, +): Promise => { + const doc = new PDFDocument({ + size: 'A4', + margins: { + top: 40, + bottom: 60, + left: 50, + right: 50, + }, + bufferPages: true, + }) + + if (!defendant) { + return Promise.reject('Defendant is missing') + } + + const sinc: Buffer[] = [] + const arraignmentDate = theCase.dateLogs?.find( + (d) => d.dateType === DateType.ARRAIGNMENT_DATE, + ) + + doc.on('data', (chunk) => sinc.push(chunk)) + + setTitle(doc, formatMessage(strings.title)) + addNormalText(doc, `${theCase.court?.name}`, 'Times-Bold', true) + + if (arraignmentDate) { + addNormalRightAlignedText( + doc, + `${formatDate(new Date(arraignmentDate.created), 'PPP')}`, + 'Times-Roman', + ) + } + + if (theCase.court?.name) { + addNormalText( + doc, + DistrictCourtLocation[theCase.court.name as DistrictCourts], + 'Times-Roman', + ) + } + + addEmptyLines(doc) + addNormalText( + doc, + defendant.name + ? `${defendant.name}, ${formatDOB( + defendant.nationalId, + defendant.noNationalId, + )}` + : 'Nafn ekki skráð', + ) + addNormalText(doc, defendant.address || 'Heimili ekki skráð') + addEmptyLines(doc) + addHugeHeading(doc, formatMessage(strings.title).toUpperCase(), 'Times-Bold') + addEmptyLines(doc) + addMediumText(doc, `Mál nr. ${theCase.courtCaseNumber}`, 'Times-Bold') + addEmptyLines(doc) + addNormalText(doc, 'Ákærandi: ', 'Times-Bold', true) + addNormalText( + doc, + theCase.prosecutor?.institution + ? theCase.prosecutor.institution.name + : 'Ekki skráður', + 'Times-Roman', + ) + addNormalText( + doc, + theCase.prosecutor + ? ` (${theCase.prosecutor.name} ${theCase.prosecutor.title})` + : 'Ekki skráður', + 'Times-Roman', + ) + addEmptyLines(doc) + addNormalText(doc, 'Ákærði: ', 'Times-Bold', true) + addNormalText(doc, defendant.name || 'Nafn ekki skráð', 'Times-Roman') + addEmptyLines(doc, 2) + + if (arraignmentDate?.date) { + addNormalText( + doc, + formatMessage(strings.arraignmentDate, { + arraignmentDate: formatDate(new Date(arraignmentDate.date), 'PPP'), + }), + 'Times-Bold', + ) + } + + if (arraignmentDate?.location) { + addNormalText( + doc, + formatMessage(strings.courtRoom, { + courtRoom: arraignmentDate.location, + }), + 'Times-Roman', + ) + } + + addNormalText(doc, formatMessage(strings.type), 'Times-Roman') + addEmptyLines(doc) + addNormalText(doc, formatMessage(strings.intro), 'Times-Bold') + addNormalText( + doc, + formatMessage( + defendant.subpoenaType === SubpoenaType.ABSENCE + ? strings.absenceIntro + : strings.arrestIntro, + ), + 'Times-Bold', + ) + + addEmptyLines(doc) + addNormalText(doc, formatMessage(strings.deadline), 'Times-Roman') + + addFooter(doc) + + doc.end() + + return new Promise((resolve) => + doc.on('end', () => resolve(Buffer.concat(sinc))), + ) +} diff --git a/apps/judicial-system/backend/src/app/messages/index.ts b/apps/judicial-system/backend/src/app/messages/index.ts index c27d0d4aa3ed..7c74f43383d6 100644 --- a/apps/judicial-system/backend/src/app/messages/index.ts +++ b/apps/judicial-system/backend/src/app/messages/index.ts @@ -7,3 +7,4 @@ export { notifications } from './notifications' export { courtUpload } from './courtUpload' export { caseFilesRecord } from './pdfCaseFilesRecord' export { indictment } from './pdfIndictment' +export { subpoena } from './pdfSubpoena' diff --git a/apps/judicial-system/backend/src/app/messages/pdfSubpoena.ts b/apps/judicial-system/backend/src/app/messages/pdfSubpoena.ts new file mode 100644 index 000000000000..90cf3dbcaaf9 --- /dev/null +++ b/apps/judicial-system/backend/src/app/messages/pdfSubpoena.ts @@ -0,0 +1,47 @@ +import { defineMessage } from '@formatjs/intl' + +export const subpoena = { + title: defineMessage({ + id: 'judicial.system.backend:pdf.subpoena.title', + defaultMessage: 'Fyrirkall', + description: 'Notaður sem titill á fyrirkalli.', + }), + arraignmentDate: defineMessage({ + id: 'judicial.system.backend:pdf.subpoena.arraignment_date', + defaultMessage: 'verður tekið fyrir á dómþingi {arraignmentDate}', + description: 'Notaður sem texti fyrir dagsetningu dómþings.', + }), + courtRoom: defineMessage({ + id: 'judicial.system.backend:pdf.subpoena.court_room', + defaultMessage: 'Staður: {courtRoom}', + description: 'Notaður sem texti fyrir stað.', + }), + type: defineMessage({ + id: 'judicial.system.backend:pdf.subpoena.type', + defaultMessage: 'Dómsathöfn: Þingfesting', + description: 'Notaður sem texti fyrir tegund.', + }), + intro: defineMessage({ + id: 'judicial.system.backend:pdf.subpoena.intro', + defaultMessage: + 'Ákærði er kvaddur til að koma fyrir dóm, hlýða á ákæru, halda uppi vörnum og sæta dómi.', + description: 'Notaður sem inngangur.', + }), + arrestIntro: defineMessage({ + id: 'judicial.system.backend:pdf.subpoena.type_intro', + defaultMessage: + 'Sæki ákærði ekki þing má hann búast við því að verða handtekinn og færður fyrir dóm.', + description: 'Notaður sem inngangur fyrir handtökufyrirkall.', + }), + absenceIntro: defineMessage({ + id: 'judicial.system.backend:pdf.subpoena.absence_intro', + defaultMessage: + 'Sæki ákærði ekki þing má hann búast við því að fjarvist hans verði metin til jafns við það að hann viðurkenni að hafa framið brot það sem hann er ákærður fyrir og dómur verði lagður á málið að honum fjarstöddum.', + description: 'Notaður sem inngangur fyrir útivistarfyrirkall.', + }), + deadline: defineMessage({ + id: 'judicial.system.backend:pdf.subpoena.deadline', + defaultMessage: 'Birtingarfrestur er þrír sólarhringar.', + description: 'Notaður sem texti fyrir frest.', + }), +} diff --git a/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts b/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts index 117bcc062f09..10271556cc3c 100644 --- a/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts +++ b/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts @@ -164,6 +164,7 @@ export class InternalCaseService { private readonly defendantService: DefendantService, @Inject(forwardRef(() => EventLogService)) private readonly eventLogService: EventLogService, + @Inject(forwardRef(() => PDFService)) private readonly pdfService: PDFService, @Inject(LOGGER_PROVIDER) private readonly logger: Logger, ) {} diff --git a/apps/judicial-system/backend/src/app/modules/case/pdf.service.ts b/apps/judicial-system/backend/src/app/modules/case/pdf.service.ts index 63fb173ca122..5f84913ff8e5 100644 --- a/apps/judicial-system/backend/src/app/modules/case/pdf.service.ts +++ b/apps/judicial-system/backend/src/app/modules/case/pdf.service.ts @@ -17,6 +17,7 @@ import { EventType, hasIndictmentCaseBeenSubmittedToCourt, isTrafficViolationCase, + SubpoenaType, type User as TUser, } from '@island.is/judicial-system/types' @@ -29,6 +30,7 @@ import { getRulingPdfAsBuffer, IndictmentConfirmation, } from '../../formatters' +import { createSubpoenaPDF } from '../../formatters/subpoenaPdf' import { AwsS3Service } from '../aws-s3' import { UserService } from '../user' import { Case } from './models/case.model' diff --git a/apps/judicial-system/web/src/components/InfoCard/InfoCardClosedIndictment.tsx b/apps/judicial-system/web/src/components/InfoCard/InfoCardClosedIndictment.tsx index eb3d2a2fcd9d..75dd469bc821 100644 --- a/apps/judicial-system/web/src/components/InfoCard/InfoCardClosedIndictment.tsx +++ b/apps/judicial-system/web/src/components/InfoCard/InfoCardClosedIndictment.tsx @@ -15,13 +15,14 @@ import { strings } from './InfoCardIndictment.strings' export interface Props { defendantInfoActionButton?: DefendantInfoActionButton + displayAppealExpirationInfo?: boolean } const InfoCardClosedIndictment: React.FC = (props) => { const { workingCase } = useContext(FormContext) const { formatMessage } = useIntl() - const { defendantInfoActionButton } = props + const { defendantInfoActionButton, displayAppealExpirationInfo } = props return ( = (props) => { ), items: workingCase.defendants, defendantInfoActionButton: defendantInfoActionButton, - displayAppealExpirationInfo: true, + displayAppealExpirationInfo, } : undefined } diff --git a/apps/judicial-system/web/src/routes/PublicProsecutor/Cases/PublicProsecutorCases.tsx b/apps/judicial-system/web/src/routes/PublicProsecutor/Cases/PublicProsecutorCases.tsx index 30b105d27406..ee4612bb5f1f 100644 --- a/apps/judicial-system/web/src/routes/PublicProsecutor/Cases/PublicProsecutorCases.tsx +++ b/apps/judicial-system/web/src/routes/PublicProsecutor/Cases/PublicProsecutorCases.tsx @@ -9,7 +9,10 @@ import { PageHeader, SharedPageLayout, } from '@island.is/judicial-system-web/src/components' -import { CaseListEntry } from '@island.is/judicial-system-web/src/graphql/schema' +import { + CaseIndictmentRulingDecision, + CaseListEntry, +} from '@island.is/judicial-system-web/src/graphql/schema' import { useCasesQuery } from '../../Shared/Cases/cases.generated' import CasesForReview from '../Tables/CasesForReview' @@ -32,7 +35,12 @@ export const PublicProsecutorCases: React.FC = () => { if ( c.state && isCompletedCase(c.state) && - !c.indictmentReviewDecision + !c.indictmentReviewDecision && + c.indictmentRulingDecision && + [ + CaseIndictmentRulingDecision.RULING, + CaseIndictmentRulingDecision.FINE, + ].includes(c.indictmentRulingDecision) ) { acc.casesForReview.push(c) } else if (c.indictmentReviewDecision) { diff --git a/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.tsx b/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.tsx index eed2faf3d792..64fde6470844 100644 --- a/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.tsx +++ b/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.tsx @@ -143,6 +143,7 @@ export const Overview = () => { } : undefined } + displayAppealExpirationInfo={true} /> {lawsBroken.size > 0 && ( diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/Cases.tsx b/apps/judicial-system/web/src/routes/Shared/Cases/Cases.tsx index cde1b2b43836..53f9be1328b2 100644 --- a/apps/judicial-system/web/src/routes/Shared/Cases/Cases.tsx +++ b/apps/judicial-system/web/src/routes/Shared/Cases/Cases.tsx @@ -277,19 +277,23 @@ export const Cases: React.FC = () => { ) : ( <> - {isProsecutionUser(user) && filter.value !== 'INVESTIGATION' && ( + {isProsecutionUser(user) && ( <> - - {isPublicProsecutor(user) && ( - + {filter.value !== 'INVESTIGATION' && ( + <> + + {isPublicProsecutor(user) && ( + + )} + )} @@ -317,13 +321,15 @@ export const Cases: React.FC = () => { )} - {isDistrictCourtUser(user) && filter.value !== 'INVESTIGATION' && ( + {isDistrictCourtUser(user) && ( <> - + {filter.value !== 'INVESTIGATION' && ( + + )} { )} {caseIsClosed ? ( - + ) : ( )} diff --git a/apps/judicial-system/web/src/types/index.ts b/apps/judicial-system/web/src/types/index.ts index 7ff58f6efd25..214127f7c17a 100644 --- a/apps/judicial-system/web/src/types/index.ts +++ b/apps/judicial-system/web/src/types/index.ts @@ -174,7 +174,6 @@ export interface NationalRegistryResponseBusiness { * We use this type so that we don't have to migrate all the code * at once and this type will be removed when we are done. */ - export interface TempIndictmentCount extends Omit { substances?: SubstanceMap | null diff --git a/apps/native/app/android/app/build.gradle b/apps/native/app/android/app/build.gradle index bf84ce0109fb..82992c91f025 100644 --- a/apps/native/app/android/app/build.gradle +++ b/apps/native/app/android/app/build.gradle @@ -119,7 +119,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode getMyVersionCode(143) - versionName "1.2.6" + versionName "1.3.0" manifestPlaceholders = [ appAuthRedirectScheme: "is.island.app" // project.config.get("BUNDLE_ID_ANDROID") ] diff --git a/apps/native/app/ios/IslandApp/Info.plist b/apps/native/app/ios/IslandApp/Info.plist index 844ab4e5baf2..ba2175dcf006 100644 --- a/apps/native/app/ios/IslandApp/Info.plist +++ b/apps/native/app/ios/IslandApp/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.2.6 + 1.3.0 CFBundleSignature ???? CFBundleURLTypes diff --git a/apps/native/app/ios/Podfile.lock b/apps/native/app/ios/Podfile.lock index a418cf1515f6..3d819f02d122 100644 --- a/apps/native/app/ios/Podfile.lock +++ b/apps/native/app/ios/Podfile.lock @@ -1,9 +1,10 @@ PODS: - - AppAuth (1.4.0): - - AppAuth/Core (= 1.4.0) - - AppAuth/ExternalUserAgent (= 1.4.0) - - AppAuth/Core (1.4.0) - - AppAuth/ExternalUserAgent (1.4.0) + - AppAuth (1.7.5): + - AppAuth/Core (= 1.7.5) + - AppAuth/ExternalUserAgent (= 1.7.5) + - AppAuth/Core (1.7.5) + - AppAuth/ExternalUserAgent (1.7.5): + - AppAuth/Core - Base64 (1.1.2) - boost (1.76.0) - CocoaAsyncSocket (7.6.5) @@ -503,8 +504,8 @@ PODS: - React-jsinspector (0.71.1) - React-logger (0.71.1): - glog - - react-native-app-auth (6.4.3): - - AppAuth (= 1.4.0) + - react-native-app-auth (7.2.0): + - AppAuth (>= 1.7.3) - React-Core - react-native-blob-util (0.16.4): - React-Core @@ -970,7 +971,7 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - AppAuth: 31bcec809a638d7bd2f86ea8a52bd45f6e81e7c7 + AppAuth: 501c04eda8a8d11f179dbe8637b7a91bb7e5d2fa Base64: cecfb41a004124895a7bcee567a89bae5a89d49b boost: 57d2868c099736d80fcd648bf211b4431e51a558 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 @@ -1044,7 +1045,7 @@ SPEC CHECKSUMS: React-jsiexecutor: 60cf272aababc5212410e4249d17cea14fc36caa React-jsinspector: ff56004b0c974b688a6548c156d5830ad751ae07 React-logger: 60a0b5f8bed667ecf9e24fecca1f30d125de6d75 - react-native-app-auth: fd1eaa667c0bc014199456d14a6440cb74de814e + react-native-app-auth: 63fa4e58c5bd29aeb974d3a06a23c5858322d533 react-native-blob-util: 60453b777610c87a22b3524032c0214e8db555db react-native-cookies: f54fcded06bb0cda05c11d86788020b43528a26c react-native-mmkv-storage: cfb6854594cfdc5f7383a9e464bb025417d1721c diff --git a/apps/native/app/package.json b/apps/native/app/package.json index aa43702e1444..0908a4c6a63a 100644 --- a/apps/native/app/package.json +++ b/apps/native/app/package.json @@ -63,7 +63,7 @@ "react": "18.2.0", "react-intl": "5.20.12", "react-native": "0.71.1", - "react-native-app-auth": "6.4.3", + "react-native-app-auth": "7.2.0", "react-native-blob-util": "0.16.4", "react-native-code-push": "7.1.0", "react-native-device-info": "10.3.0", diff --git a/apps/native/app/src/components/offline/offline-banner.tsx b/apps/native/app/src/components/offline/offline-banner.tsx index f17c66a33140..60f01b78f504 100644 --- a/apps/native/app/src/components/offline/offline-banner.tsx +++ b/apps/native/app/src/components/offline/offline-banner.tsx @@ -4,7 +4,7 @@ import { Animated, Easing, SafeAreaView } from 'react-native' import { Navigation } from 'react-native-navigation' import styled from 'styled-components/native' import { getIntl } from '../../contexts/i18n-provider' -import { useOfflineActions, useOfflineStore } from '../../stores/offline-store' +import { useOfflineActions } from '../../stores/offline-store' import { ComponentRegistry as CR } from '../../utils/component-registry' const TranslateYValue = 200 diff --git a/apps/native/app/src/graphql/client.ts b/apps/native/app/src/graphql/client.ts index 97b98e2d2f51..4149a0091e62 100644 --- a/apps/native/app/src/graphql/client.ts +++ b/apps/native/app/src/graphql/client.ts @@ -161,7 +161,7 @@ const cache = new InMemoryCache({ }, }, }, - Document: { + DocumentV2: { fields: { archived: { read(_value, { readField, variables }) { diff --git a/apps/native/app/src/graphql/fragments/document.fragment.graphql b/apps/native/app/src/graphql/fragments/document.fragment.graphql index ede2164f6c57..e074389e3a53 100644 --- a/apps/native/app/src/graphql/fragments/document.fragment.graphql +++ b/apps/native/app/src/graphql/fragments/document.fragment.graphql @@ -1,15 +1,16 @@ directive @client on FIELD -fragment ListDocument on Document { +fragment ListDocument on DocumentV2 { id subject - senderName - senderNatReg - date - fileType - url + publicationDate + downloadUrl opened categoryId bookmarked + sender { + id + name + } archived @client } diff --git a/apps/native/app/src/graphql/queries/inbox.graphql b/apps/native/app/src/graphql/queries/inbox.graphql index 36b29b11ba8d..2ecdf2458686 100644 --- a/apps/native/app/src/graphql/queries/inbox.graphql +++ b/apps/native/app/src/graphql/queries/inbox.graphql @@ -12,28 +12,28 @@ query ListOrganizations { } } -query ListDocuments($input: GetDocumentListInput!) { - listDocumentsV2(input: $input) { +query ListDocuments($input: DocumentsV2DocumentsInput!) { + documentsV2(input: $input) { data { ...ListDocument } totalCount + unreadCount } } -query GetDocument($input: GetDocumentInput!) { - getDocument(input: $input) { - fileType - content - url - html +query GetDocument($input: DocumentInput!) { + documentV2(input: $input) { + ...ListDocument + content { + type + value + } } } -mutation PostMailActionMutation($input: PostMailActionResolverInput!) { - postMailAction(input: $input) { +mutation PostMailActionMutation($input: DocumentsV2MailActionInput!) { + postMailActionV2(input: $input) { success - messageId - action } } diff --git a/apps/native/app/src/hoc/offline-hoc.tsx b/apps/native/app/src/hoc/offline-hoc.tsx index 24bbe519b1d5..12ac92c7bf24 100644 --- a/apps/native/app/src/hoc/offline-hoc.tsx +++ b/apps/native/app/src/hoc/offline-hoc.tsx @@ -20,15 +20,13 @@ export const OfflineHoc = ({ children }: OfflineHocProps) => { const lockScreenActivatedAt = useAuthStore( ({ lockScreenActivatedAt }) => lockScreenActivatedAt, ) - const userInfo = useAuthStore(({ userInfo }) => userInfo) useEffect(() => { if ( bannerVisible && !bannerHasBeenShown && !isConnected && - !lockScreenActivatedAt && - userInfo + lockScreenActivatedAt === null ) { void impactAsync(ImpactFeedbackStyle.Heavy) void Navigation.showOverlay({ @@ -39,13 +37,7 @@ export const OfflineHoc = ({ children }: OfflineHocProps) => { }) setBannerHasBeenShown(true) } - }, [ - bannerVisible, - bannerHasBeenShown, - isConnected, - lockScreenActivatedAt, - userInfo, - ]) + }, [bannerVisible, bannerHasBeenShown, isConnected, lockScreenActivatedAt]) return <>{children} } diff --git a/apps/native/app/src/lib/post-mail-action.ts b/apps/native/app/src/lib/post-mail-action.ts index 58cf25c2cf35..166f406bf6ae 100644 --- a/apps/native/app/src/lib/post-mail-action.ts +++ b/apps/native/app/src/lib/post-mail-action.ts @@ -21,17 +21,18 @@ export async function toggleAction( mutation: PostMailActionMutationDocument, variables: { input: { - messageId, + documentIds: [messageId], action, }, }, refetchQueries: refetch ? [ListDocumentsDocument] : undefined, update(cache, { data }) { const id = cache.identify({ - __typename: 'Document', + __typename: 'DocumentV2', id: messageId, }) - const success = data?.postMailAction?.success + + const success = data?.postMailActionV2?.success if (!success) { return } diff --git a/apps/native/app/src/messages/en.ts b/apps/native/app/src/messages/en.ts index 2490a8017358..6792e2614ac4 100644 --- a/apps/native/app/src/messages/en.ts +++ b/apps/native/app/src/messages/en.ts @@ -281,6 +281,9 @@ export const en: TranslatedMessages = { 'notifications.markAllAsRead': 'Mark all as read', 'notifications.settings': 'My settings', 'notifications.errorUnknown': 'Error occurred while loading notifications', + 'notifications.emptyListTitle': 'No notifications', + 'notifications.emptyListDescription': + 'When you receive notifications, they will appear here.', // profile 'profile.screenTitle': 'More', diff --git a/apps/native/app/src/messages/is.ts b/apps/native/app/src/messages/is.ts index f6754c762e4c..c3bcdb5d8bf4 100644 --- a/apps/native/app/src/messages/is.ts +++ b/apps/native/app/src/messages/is.ts @@ -415,6 +415,9 @@ export const is = { 'notifications.markAllAsRead': 'Merkja allt lesið', 'notifications.settings': 'Mínar stillingar', 'notifications.errorUnknown': 'Villa kom upp við að sækja tilkynningar', + 'notifications.emptyListTitle': 'Engar tilkynningar', + 'notifications.emptyListDescription': + 'Þegar þú færð sendar tilkynningar þá birtast þær hér.', // applications screen 'applications.title': 'Umsóknir', diff --git a/apps/native/app/src/screens/app-lock/app-lock.tsx b/apps/native/app/src/screens/app-lock/app-lock.tsx index 79fab7333bd7..ae72cf99b3be 100644 --- a/apps/native/app/src/screens/app-lock/app-lock.tsx +++ b/apps/native/app/src/screens/app-lock/app-lock.tsx @@ -89,15 +89,18 @@ export const AppLockScreen: NavigationFunctionComponent<{ lockScreenComponentId: undefined, })) }, []) - const unlockApp = useCallback(() => { Animated.spring(av, { toValue: 0, useNativeDriver: true, delay: 100, }).start(() => { - resetLockScreen() - void Navigation.dismissAllOverlays() + // We want to reset lockScreenActivatedAt to null here to trigger offline banner if offline + authStore.setState(() => ({ + lockScreenActivatedAt: null, + lockScreenComponentId: undefined, + })) + void Navigation.dismissOverlay(componentId) av.setValue(1) }) }, [componentId]) diff --git a/apps/native/app/src/screens/document-detail/document-detail.tsx b/apps/native/app/src/screens/document-detail/document-detail.tsx index ae696f46eeed..635f4a2907e1 100644 --- a/apps/native/app/src/screens/document-detail/document-detail.tsx +++ b/apps/native/app/src/screens/document-detail/document-detail.tsx @@ -19,10 +19,9 @@ import Share from 'react-native-share' import WebView from 'react-native-webview' import styled from 'styled-components/native' import { - Document, + DocumentV2, ListDocumentFragmentDoc, useGetDocumentQuery, - useListDocumentsLazyQuery, } from '../../graphql/types/schema' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { useConnectivityIndicator } from '../../hooks/use-connectivity-indicator' @@ -178,48 +177,44 @@ export const DocumentDetailScreen: NavigationFunctionComponent<{ const [accessToken, setAccessToken] = useState() const [error, setError] = useState(false) - const doc = useFragment_experimental({ + // Check if we have the document in the cache + const doc = useFragment_experimental({ fragment: ListDocumentFragmentDoc, from: { - __typename: 'Document', + __typename: 'DocumentV2', id: docId, }, returnPartialData: true, }) - const [getListDocuments] = useListDocumentsLazyQuery() + // Fetch the document to get the content information const docRes = useGetDocumentQuery({ variables: { input: { id: docId, }, }, + fetchPolicy: 'no-cache', }) const Document = { - ...(docRes.data?.getDocument || {}), - ...doc.data, + ...(doc?.data || {}), + ...(docRes.data?.documentV2 || {}), } - useEffect(() => { - if (doc.missing) { - void getListDocuments({ - variables: { - input: { - page: 1, - pageSize: 50, - }, - }, - }) - } - }, [doc]) - const [visible, setVisible] = useState(false) const [loaded, setLoaded] = useState(false) const [pdfUrl, setPdfUrl] = useState('') const [touched, setTouched] = useState(false) - const hasPdf = Document.fileType?.toLocaleLowerCase() === 'pdf' - const isHtml = typeof Document.html === 'string' && Document.html !== '' + + const loading = docRes.loading || !accessToken + const fileTypeLoaded = !!Document?.content?.type + const hasError = error || docRes.error + + const hasPdf = Document?.content?.type.toLocaleLowerCase() === 'pdf' + const isHtml = + Document?.content?.type.toLocaleLowerCase() === 'html' && + Document.content?.value !== '' useConnectivityIndicator({ componentId, @@ -247,16 +242,16 @@ export const DocumentDetailScreen: NavigationFunctionComponent<{ ) setTouched(true) } - if (buttonId === ButtonRegistry.ShareButton) { + if (buttonId === ButtonRegistry.ShareButton && loaded) { if (Platform.OS === 'android') { authStore.setState({ noLockScreenUntilNextAppStateActive: true }) } Share.open({ title: Document.subject!, subject: Document.subject!, - message: `${Document.senderName!} \n ${Document.subject!}`, + message: `${Document.sender!.name!} \n ${Document.subject!}`, type: hasPdf ? 'application/pdf' : undefined, - url: hasPdf ? `file://${pdfUrl}` : Document.url!, + url: hasPdf ? `file://${pdfUrl}` : Document.downloadUrl!, }) } }, componentId) @@ -281,7 +276,7 @@ export const DocumentDetailScreen: NavigationFunctionComponent<{ // Lets mark the document as read client.cache.modify({ id: client.cache.identify({ - __typename: 'Document', + __typename: 'DocumentV2', id: Document.id, }), fields: { @@ -305,8 +300,6 @@ export const DocumentDetailScreen: NavigationFunctionComponent<{ } }, []) - const loading = docRes.loading || !accessToken - const fadeAnim = useRef(new Animated.Value(0)).current React.useEffect(() => { @@ -323,12 +316,16 @@ export const DocumentDetailScreen: NavigationFunctionComponent<{ <>
} + title={Document.sender?.name ?? ''} + date={ + Document.publicationDate ? ( + + ) : undefined + } message={Document.subject} - isLoading={loading} + isLoading={loading && !Document.subject} hasBorder={false} - logo={getOrganizationLogoUrl(Document.senderName!, 75)} + logo={getOrganizationLogoUrl(Document.sender?.name ?? '', 75)} /> @@ -343,42 +340,48 @@ export const DocumentDetailScreen: NavigationFunctionComponent<{ opacity: fadeAnim, }} > - {isHtml ? ( - { - setLoaded(true) - }} - /> - ) : hasPdf ? ( - - {visible && accessToken && ( - { - setPdfUrl(filePath) - setLoaded(true) - }} - onError={() => { - setLoaded(true) - setError(true) - }} - /> - )} - - ) : ( - { - setLoaded(true) - }} - /> - )} + {fileTypeLoaded && + !error && + (isHtml ? ( + { + setLoaded(true) + }} + /> + ) : hasPdf ? ( + + {visible && accessToken && ( + { + setPdfUrl(filePath) + setLoaded(true) + }} + onError={() => { + setLoaded(true) + setError(true) + }} + /> + )} + + ) : ( + { + setLoaded(true) + }} + onError={() => { + setLoaded(true) + setError(true) + }} + /> + ))} - {(!loaded || !accessToken || error) && ( + {(!loaded || !accessToken || hasError) && ( - {error ? ( + {hasError ? ( ) : ( { + ({ item, listParams }: { item: DocumentV2; listParams: any }) => { const { getOrganizationLogoUrl } = useOrganizationsStore() const [starred, setStarred] = useState(!!item.bookmarked) useEffect(() => setStarred(!!item.bookmarked), [item.bookmarked]) @@ -105,22 +103,26 @@ const PressableListItem = React.memo( highlightColor={theme.shade.shade400} onPress={() => navigateTo(`/inbox/${item.id}`, { - title: item.senderName, + title: item.sender.name, listParams, }) } > { toggleAction(!item.bookmarked ? 'bookmark' : 'unbookmark', item.id) setStarred(!item.bookmarked) }} - icon={getOrganizationLogoUrl(item.senderName, 75)} + icon={ + item.sender.name && getOrganizationLogoUrl(item.sender.name, 75) + } /> ) @@ -139,23 +141,6 @@ function useThrottleState(state: string, delay = 500) { return throttledState } -const useUnreadCount = () => { - const res = useListDocumentsQuery({ - fetchPolicy: 'cache-first', - variables: { - input: { - page: 1, - pageSize: 50, - opened: false, - }, - }, - }) - const unopened = res?.data?.listDocumentsV2?.data?.filter( - (item) => item.opened === false, - ) - return unopened?.length ?? 0 -} - type Filters = { opened?: boolean archived?: boolean @@ -175,7 +160,7 @@ function applyFilters(filters?: Filters) { function useInboxQuery(incomingFilters?: Filters) { const [filters, setFilters] = useState(applyFilters(incomingFilters)) const [page, setPage] = useState(1) - const [data, setData] = useState() + const [data, setData] = useState() const [refetching, setRefetching] = useState(true) const [loading, setLoading] = useState(false) const [refetcher, setRefetcher] = useState(0) @@ -229,12 +214,13 @@ function useInboxQuery(incomingFilters?: Filters) { setData((prevData) => ({ data: [ ...(prevData?.data ?? []), - ...(data.listDocumentsV2?.data ?? []), + ...(data.documentsV2?.data ?? []), ], - totalCount: data.listDocumentsV2?.totalCount, + totalCount: data.documentsV2?.totalCount ?? 0, + unreadCount: data.documentsV2?.unreadCount ?? 0, })) } else { - setData(data.listDocumentsV2) + setData(data.documentsV2) } } }) @@ -289,7 +275,6 @@ export const InboxScreen: NavigationFunctionComponent<{ const [query, setQuery] = useState('') const queryString = useThrottleState(query) const theme = useTheme() - const unreadCount = useUnreadCount() const [visible, setVisible] = useState(false) const [refetching, setRefetching] = useState(false) @@ -308,6 +293,7 @@ export const InboxScreen: NavigationFunctionComponent<{ } }, }) + const unreadCount = res?.data?.unreadCount ?? 0 useConnectivityIndicator({ componentId, @@ -324,7 +310,7 @@ export const InboxScreen: NavigationFunctionComponent<{ res.refetch() }, [refresh]) - const items = res.data?.data ?? [] + const items = useMemo(() => res.data?.data ?? [], [res.data]) const isSearch = ui.inboxQuery.length > 2 useActiveTabItemPress(0, () => { @@ -383,7 +369,7 @@ export const InboxScreen: NavigationFunctionComponent<{ } return ( theme.spacing[3]}px; @@ -47,10 +55,14 @@ const ButtonWrapper = styled.View` flex-direction: row; margin-horizontal: ${({ theme }) => theme.spacing[2]}px; margin-top: ${({ theme }) => theme.spacing[2]}px; + margin-bottom: ${({ theme }) => theme.spacing[2]}px; ` const DEFAULT_PAGE_SIZE = 50 +const FALLBACK_ICON_URL = + 'https://images.ctfassets.net/8k0h54kbe6bj/6XhCz5Ss17OVLxpXNVDxAO/d3d6716bdb9ecdc5041e6baf68b92ba6/coat_of_arms.svg' + const { getNavigationOptions, useNavigationOptions } = createNavigationOptionHooks(() => ({ topBar: { @@ -62,7 +74,12 @@ type NotificationItem = NonNullable< NonNullable['data'] >[0] -type ListItem = SkeletonItem | NotificationItem +export type EmptyItem = { + id: string + __typename: 'Empty' +} + +type ListItem = SkeletonItem | NotificationItem | EmptyItem export const NotificationsScreen: NavigationFunctionComponent = ({ componentId, @@ -123,6 +140,10 @@ export const NotificationsScreen: NavigationFunctionComponent = ({ return createSkeletonArr(9) } + if (data?.userNotifications?.data?.length === 0) { + return [{ id: '0', __typename: 'Empty' }] + } + return data?.userNotifications?.data || [] }, [data, loading]) @@ -185,6 +206,26 @@ export const NotificationsScreen: NavigationFunctionComponent = ({ return } + if (item.__typename === 'Empty') { + return ( + + + } + /> + + ) + } + return ( onNotificationPress(item)} testID={testIDs.NOTIFICATION_CARD_BUTTON} @@ -219,40 +260,6 @@ export const NotificationsScreen: NavigationFunctionComponent = ({ showLoading={loading && !!data} /> - -