diff --git a/.github/workflows/build-image-on-pr.yaml b/.github/workflows/build-image-on-pr.yaml deleted file mode 100644 index d510f1c2110..00000000000 --- a/.github/workflows/build-image-on-pr.yaml +++ /dev/null @@ -1,25 +0,0 @@ -name: Docker build -on: - pull_request: - branches: [ main ] - types: [opened, synchronize, reopened] - paths: - - 'frontend/**' - - '!frontend/stats/**' - - 'backend/**' - - '.github/workflows/build-image-on-pr.yaml' - workflow_dispatch: - -jobs: - docker-build: - name: Build image - runs-on: ubuntu-latest - container: - image: docker:latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - name: Build image - run: | - docker build . diff --git a/.github/workflows/frontend-unit-tests.yml b/.github/workflows/frontend-unit-tests.yml index 1eac67d7483..379c27e96dd 100644 --- a/.github/workflows/frontend-unit-tests.yml +++ b/.github/workflows/frontend-unit-tests.yml @@ -2,6 +2,8 @@ name: Frontend Tests on: push: + branches: + - main paths: - 'frontend/**' - '!frontend/testing/cypress/**' @@ -9,9 +11,18 @@ on: - 'testdata/**' - '.github/workflows/frontend-unit-tests.yml' - 'package.json' + pull_request: + paths: + - 'frontend/**' + - '!frontend/testing/cypress/**' + - '!frontend/stats/**' + - 'testdata/**' + - '.github/workflows/frontend-unit-tests.yml' + - 'package.json' + workflow_dispatch: concurrency: - group: ${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: @@ -63,7 +74,7 @@ jobs: run: yarn typecheck - name: 'Running Eslint' - run: yarn lint + run: yarn lint --cache - name: 'Checking code style' run: yarn run codestyle:check @@ -81,7 +92,13 @@ jobs: uses: ./.github/actions/yarn-install - name: 'Running Unit Tests' - run: yarn test:ci + run: | + if [ "${{ github.event_name }}" == "pull_request" ]; then + git fetch origin ${{ github.event.pull_request.base.ref }} + yarn test:ci --changedSince=origin/${{ github.event.pull_request.base.ref }} + else + yarn test:ci + fi - name: 'Upload coverage reports to Codecov' uses: codecov/codecov-action@v4 @@ -90,18 +107,3 @@ jobs: with: directory: frontend/coverage fail_ci_if_error: true - - build: - name: 'Building' - runs-on: ubuntu-latest - steps: - - name: 'Checking Out Code' - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: 'Installing Dependencies' - uses: ./.github/actions/yarn-install - - - name: 'Building' - run: yarn build diff --git a/.github/workflows/helm-chart-push.yaml b/.github/workflows/helm-chart-push.yaml new file mode 100644 index 00000000000..c5fac2995e1 --- /dev/null +++ b/.github/workflows/helm-chart-push.yaml @@ -0,0 +1,51 @@ +name: Eid logger helm build and push +on: + push: + branches: [ main ] + paths: + - 'eidlogger/**' + - '.github/workflows/helm-chart-push.yaml' + - 'charts/eid-logger/**' + workflow_dispatch: + +permissions: + id-token: write + contents: read + +jobs: + helm-build-push: + name: Helm build and push + runs-on: ubuntu-latest + env: + REGISTRY: altinntjenestercontainerregistry.azurecr.io + REPOSITORY: eid-logger + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID_FC }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID_FC }} + AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID_FC }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - name: 'Azure login' + uses: azure/login@v2 + with: + client-id: ${{ env.AZURE_CLIENT_ID }} + tenant-id: ${{ env.AZURE_TENANT_ID }} + subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }} + + - name: 'Install helm' + uses: azure/setup-helm@v4.2.0 + with: + version: '3.15.3' + id: install + + - name: Log in to Container registry + run: | + az acr login --name ${{ env.REGISTRY }} --expose-token --output tsv --query accessToken --subscription ${{ secrets.AZURE_SUBSCRIPTION_ID_FC }} --only-show-errors | helm registry login ${{ env.REGISTRY }} --username 00000000-0000-0000-0000-000000000000 --password-stdin + - name: Helm build + run: | + helm package charts/eid-logger --version 0.1.0+${{ github.sha }} + - name: Helm push + run: | + helm push eid-logger-0.1.0+${{ github.sha }}.tgz oci://${{ env.REGISTRY }}/charts + diff --git a/.github/workflows/playwright-resourceadm.yml b/.github/workflows/playwright-resourceadm.yml new file mode 100644 index 00000000000..c06a4493678 --- /dev/null +++ b/.github/workflows/playwright-resourceadm.yml @@ -0,0 +1,72 @@ +name: Resourceadm Playwright Tests in Dev + +on: + push: + branches: + - main + paths: + - 'frontend/resourceadm/**' + - '.github/workflows/playwright-resourceadm.yml' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + playwright-run: + name: 'Resourceadm Playwright Tests' + runs-on: ubuntu-latest + + steps: + - name: 'Checking Out Code' + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-node@v4 + with: + cache: 'yarn' + + - name: Get yarn cache directory path + working-directory: frontend + id: yarn-cache-dir-path + run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT + + - uses: actions/cache@v4 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Attempt to wait for deploy to environment (15 minutes sleep) + run: | + if [ "${{ github.run_attempt }}" != "1" ]; then + echo "Skip waiting for deploy, since this is a re-run of the pipeline." + else + sleep 15m + fi + + - name: Resourceadm Playwright run + working-directory: frontend/resourceadm/testing/playwright + env: + PLAYWRIGHT_TEST_BASE_URL: 'https://dev.altinn.studio' + PLAYWRIGHT_RESOURCES_ORGANIZATION: ttd + PLAYWRIGHT_RESOURCES_REPO_NAME: ttd-resources + PLAYWRIGHT_USER: 'AutoTest' + PLAYWRIGHT_PASS: ${{ secrets.AUTO_TEST_USER_PWD }} + GITEA_ACCESS_TOKEN: ${{ secrets.AUTO_TEST_USER_TOKEN_DEV }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + yarn + yarn install --immutable --inline-builds + yarn playwright install --with-deps + yarn resourceadm:playwright:test:all + + - name: Store artifacts + uses: actions/upload-artifact@v4 + if: failure() + with: + name: playwright-resourceadm-screenshots + path: frontend/resourceadm/testing/playwright/test-results diff --git a/.github/workflows/run-playwright-on-pr.yaml b/.github/workflows/run-playwright-on-pr.yaml index d794c8119e3..bde4dc87e81 100644 --- a/.github/workflows/run-playwright-on-pr.yaml +++ b/.github/workflows/run-playwright-on-pr.yaml @@ -15,6 +15,10 @@ on: - 'development/**' workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: playwright-tests: name: Build environment and run e2e test @@ -22,10 +26,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - name: 'Checking Out Code' + uses: actions/checkout@v4 with: fetch-depth: 0 + - name: 'Installing Dependencies' + uses: ./.github/actions/yarn-install + - name: Generate .env file run: | echo PLAYWRIGHT_TEST_APP=autodeploy-v3 >> .env @@ -43,15 +51,6 @@ jobs: echo COMMIT= >> .env echo IGNORE_DOCKER_DNS_LOOKUP=true >> .env - - name: Build all images - run: | - docker compose build --no-cache - - - name: Install node - uses: actions/setup-node@v4 - with: - cache: 'yarn' - - name: Run setup.js script run: | node ./development/setup.js @@ -61,15 +60,9 @@ jobs: env: environment: local run: | - yarn - yarn playwright install --with-deps yarn setup:playwright yarn playwright:test:all - - name: Stop compose file - if: always() - run: docker-compose down - - name: Store artifacts uses: actions/upload-artifact@v4 if: failure() diff --git a/.github/workflows/run-playwright-resourceadm-on-pr.yml b/.github/workflows/run-playwright-resourceadm-on-pr.yml new file mode 100644 index 00000000000..a857ecdd92d --- /dev/null +++ b/.github/workflows/run-playwright-resourceadm-on-pr.yml @@ -0,0 +1,71 @@ +name: Resource admin playwright tests on pr +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened] + paths: + - 'frontend/resourceadm/**' + - '.github/workflows/run-playwright-resourceadm-on-pr.yaml' + workflow_dispatch: + +jobs: + playwright-tests: + name: Build environment and run e2e test + timeout-minutes: 25 + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Generate .env file + run: | + echo PLAYWRIGHT_TEST_APP=autodeploy-v3 >> .env + echo DEVELOP_APP_DEVELOPMENT=0 >> .env + echo DEVELOP_RESOURCE_ADMIN=0 >> .env + echo DEVELOP_BACKEND=0 >> .env + echo DEVELOP_DASHBOARD=0 >> .env + echo DEVELOP_PREVIEW=0 >> .env + echo GITEA_ADMIN_PASS=g9wDIG@6gf >> .env + echo GITEA_ADMIN_USER=localg1iteaadmin >> .env + echo GITEA_CYPRESS_USER=cypress_testuser >> .env + echo GITEA_CYPRESS_PASS=g9wDIG@6gf >> .env + echo GITEA_ORG_USER=ttd >> .env + echo POSTGRES_PASSWORD=kyeDIG@eip >> .env + echo COMMIT= >> .env + echo IGNORE_DOCKER_DNS_LOOKUP=true >> .env + + - name: Build all images + run: | + docker compose build --no-cache + + - name: Install node + uses: actions/setup-node@v4 + with: + cache: 'yarn' + + - name: Run setup.js script + run: | + node ./development/setup.js + + - name: Playwright resourceadm run + working-directory: frontend/resourceadm/testing/playwright + env: + environment: local + run: | + yarn + yarn playwright install --with-deps + yarn resourceadm:setup:playwright + yarn resourceadm:playwright:test:all + + - name: Stop compose file + if: always() + run: docker-compose down + + - name: Store artifacts + uses: actions/upload-artifact@v4 + if: failure() + with: + name: playwright-resourceadm-screenshots + path: frontend/testing/playwright/test-results diff --git a/Dockerfile b/Dockerfile index fa56bf7a997..1fce354f7f1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,46 @@ # Building studio frontend FROM node:lts-alpine AS generate-studio-frontend WORKDIR /build -COPY . . -RUN corepack enable + +COPY ./package.json yarn.lock ./ +COPY ./.yarnrc.yml ./.yarnrc.yml +COPY ./.yarn/releases ./.yarn/releases + +COPY ./development/azure-devops-mock/package.json ./development/azure-devops-mock/ +COPY ./frontend/app-development/package.json ./frontend/app-development/ +COPY ./frontend/app-preview/package.json ./frontend/app-preview/ +COPY ./frontend/dashboard/package.json ./frontend/dashboard/ +COPY ./frontend/language/package.json ./frontend/language/ +COPY ./frontend/libs/studio-components/package.json ./frontend/libs/studio-components/ +COPY ./frontend/libs/studio-icons/package.json ./frontend/libs/studio-icons/ +COPY ./frontend/libs/studio-pure-functions/package.json ./frontend/libs/studio-pure-functions/ +COPY ./frontend/packages/policy-editor/package.json ./frontend/packages/policy-editor/ +COPY ./frontend/packages/process-editor/package.json ./frontend/packages/process-editor/ +COPY ./frontend/packages/schema-editor/package.json ./frontend/packages/schema-editor/ +COPY ./frontend/packages/schema-model/package.json ./frontend/packages/schema-model/ +COPY ./frontend/packages/shared/package.json ./frontend/packages/shared/ +COPY ./frontend/packages/text-editor/package.json ./frontend/packages/text-editor/ +COPY ./frontend/packages/ux-editor/package.json ./frontend/packages/ux-editor/ +COPY ./frontend/packages/ux-editor-v3/package.json ./frontend/packages/ux-editor-v3/ +COPY ./frontend/resourceadm/package.json ./frontend/resourceadm/ +COPY ./frontend/resourceadm/testing/playwright/package.json ./frontend/resourceadm/testing/playwright/ +COPY ./frontend/studio-root/package.json ./frontend/studio-root/ +COPY ./frontend/testing/cypress/package.json ./frontend/testing/cypress/ +COPY ./frontend/testing/mockend/package.json ./frontend/testing/mockend/ +COPY ./frontend/testing/playwright/package.json ./frontend/testing/playwright/ + RUN yarn --immutable + +COPY . . RUN yarn build # Building the backend FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS generate-studio-backend WORKDIR /build COPY backend . -RUN dotnet build src/Designer/Designer.csproj -c Release -o /app_output RUN dotnet publish src/Designer/Designer.csproj -c Release -o /app_output RUN rm -f /app_output/Altinn.Studio.Designer.staticwebassets.runtime.json + # Prepare app template WORKDIR /app_template RUN apk add jq zip diff --git a/backend/packagegroups/NuGet.props b/backend/packagegroups/NuGet.props index 9fbb59e6aa8..c6ba4308f16 100644 --- a/backend/packagegroups/NuGet.props +++ b/backend/packagegroups/NuGet.props @@ -2,9 +2,9 @@ - - - + + + @@ -19,7 +19,7 @@ - + @@ -27,9 +27,9 @@ - + - + @@ -41,12 +41,12 @@ - + - - + + @@ -55,7 +55,7 @@ - - + + diff --git a/backend/src/Designer/Controllers/AppDevelopmentController.cs b/backend/src/Designer/Controllers/AppDevelopmentController.cs index 714114556bb..ec0f288c121 100644 --- a/backend/src/Designer/Controllers/AppDevelopmentController.cs +++ b/backend/src/Designer/Controllers/AppDevelopmentController.cs @@ -326,16 +326,17 @@ public async Task GetLayoutSets(string org, string app, Cancellat /// /// Unique identifier of the organisation responsible for the app. /// Application identifier which is unique within an organisation. - /// The config needed for the layout set to be added to layout-sets.json + /// Includes the connected taskType and the actual config needed for the layout set to be added to layout-sets.json. /// An that observes if operation is cancelled. [HttpPost] [UseSystemTextJson] [Route("layout-set/{layoutSetIdToUpdate}")] - public async Task AddLayoutSet(string org, string app, [FromBody] LayoutSetConfig layoutSet, CancellationToken cancellationToken) + public async Task AddLayoutSet(string org, string app, [FromBody] LayoutSetPayload layoutSetPayload, CancellationToken cancellationToken) { string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext); var editingContext = AltinnRepoEditingContext.FromOrgRepoDeveloper(org, app, developer); - LayoutSets layoutSets = await _appDevelopmentService.AddLayoutSet(editingContext, layoutSet, cancellationToken); + bool layoutIsInitialForPaymentTask = layoutSetPayload.TaskType == TaskType.Payment; + LayoutSets layoutSets = await _appDevelopmentService.AddLayoutSet(editingContext, layoutSetPayload.LayoutSetConfig, layoutIsInitialForPaymentTask, cancellationToken); return Ok(layoutSets); } @@ -515,7 +516,7 @@ public ActionResult GetOptionListIds(string org, string app) { string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext); AltinnAppGitRepository altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, app, developer); - string[] optionListIds = altinnAppGitRepository.GetOptionListIds(); + string[] optionListIds = altinnAppGitRepository.GetOptionsListIds(); return Ok(optionListIds); } catch (LibGit2Sharp.NotFoundException) diff --git a/backend/src/Designer/Controllers/OptionsController.cs b/backend/src/Designer/Controllers/OptionsController.cs new file mode 100644 index 00000000000..b11d7cc2c26 --- /dev/null +++ b/backend/src/Designer/Controllers/OptionsController.cs @@ -0,0 +1,127 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Altinn.Studio.Designer.Helpers; +using Altinn.Studio.Designer.Models; +using Altinn.Studio.Designer.Services.Interfaces; +using LibGit2Sharp; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Altinn.Studio.Designer.Controllers; + +/// +/// Controller containing actions related to options (code lists). +/// +[ApiController] +[Authorize] +[AutoValidateAntiforgeryToken] +[Route("designer/api/{org}/{repo:regex(^(?!datamodels$)[[a-z]][[a-z0-9-]]{{1,28}}[[a-z0-9]]$)}/options")] +public class OptionsController : ControllerBase +{ + private readonly IOptionsService _optionsService; + + /// + /// Initializes a new instance of the class. + /// + /// The options service. + public OptionsController(IOptionsService optionsService) + { + _optionsService = optionsService; + } + + /// + /// Fetches the IDs of the options lists belonging to the app. + /// + /// Unique identifier of the organisation responsible for the app. + /// Application identifier which is unique within an organisation. + /// Array of options list's IDs. Empty array if none are found + [HttpGet] + [Produces("application/json")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult GetOptionsListIds(string org, string repo) + { + string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext); + + string[] optionsListIds = _optionsService.GetOptionsListIds(org, repo, developer); + + return Ok(optionsListIds); + } + + /// + /// Fetches a specific option list. + /// + /// Unique identifier of the organisation responsible for the app. + /// Application identifier which is unique within an organisation. + /// Name of the option list. + /// A that observes if operation is cancelled. + [HttpGet] + [Produces("application/json")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Route("{optionsListId}")] + public async Task>> GetOptionsList(string org, string repo, [FromRoute] string optionsListId, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext); + + try + { + List