diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index caaab92b..b3e67aef 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -1,66 +1,68 @@ +name: Static code analysis +on: + pull_request: + branches: + - main + - next +jobs: + code-analysis: name: Static code analysis - on: - pull_request: - branches: - - main - - next - jobs: - code-analysis: - name: Static code analysis - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Set up JDK 17 - uses: actions/setup-java@v3 - with: - java-version: 17 - distribution: 'zulu' # Alternative distribution options are available. - - - name: Cache SonarCloud packages - uses: actions/cache@v1 - with: - path: ~\sonar\cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - - - name: Cache SonarCloud scanner - id: cache-sonar-scanner - uses: actions/cache@v3 - with: - path: ~\sonar\cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - - - name: Cache SonarCloud coverage - id: cache-sonar-coverage - uses: actions/cache@v3 - with: - path: ~\sonar\cache - key: ${{ runner.os }}-coverage - restore-keys: ${{ runner.os }}-coverage + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 - - name: Install SonarCloud scanners - if: steps.cache-sonar-scanner.outputs.cache-hit != 'true' - run: dotnet tool install --global dotnet-sonarscanner - - - name: Install SonarCloud coverage - if: steps.cache-sonar-coverage.outputs.cache-hit != 'true' - run: dotnet tool install --global dotnet-coverage + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: 17 + distribution: "zulu" # Alternative distribution options are available. - - name: Build, Test and Analyze - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: | - dotnet-sonarscanner begin \ - /k:"DFE-Digital_childrens-social-care-cpd" \ - /o:"dfe-digital" \ - /d:sonar.qualitygate.wait=true \ - /d:sonar.cs.vscoveragexml.reportsPaths=coverage.xml \ - /d:sonar.exclusions="**/*.css,**/*.scss,**/Models/*,**/Program.cs,**/WebApplicationBuilderExtensions.cs,**/GraphQL/Queries/*" \ - /d:sonar.test.exclusions="Childrens-Social-Care-CPD-Tests/**/*" \ - /d:sonar.token="${{ secrets.SONAR_TOKEN }}" \ - /d:sonar.host.url="https://sonarcloud.io" - dotnet build --no-incremental - dotnet-coverage collect --settings dotnet-cover-config.xml -f xml -o coverage.xml "dotnet test" - dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" \ No newline at end of file + - name: Cache SonarCloud packages + uses: actions/cache@v1 + with: + path: ~\sonar\cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + + - name: Cache SonarCloud scanner + id: cache-sonar-scanner + uses: actions/cache@v3 + with: + path: ~\sonar\cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + + - name: Cache SonarCloud coverage + id: cache-sonar-coverage + uses: actions/cache@v3 + with: + path: ~\sonar\cache + key: ${{ runner.os }}-coverage + restore-keys: ${{ runner.os }}-coverage + + - name: Install SonarCloud scanners + if: steps.cache-sonar-scanner.outputs.cache-hit != 'true' + run: dotnet tool install --global dotnet-sonarscanner + + - name: Install SonarCloud coverage + if: steps.cache-sonar-coverage.outputs.cache-hit != 'true' + run: dotnet tool install --global dotnet-coverage + + - name: Build, Test and Analyze + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: | + dotnet-sonarscanner begin \ + /k:"DFE-Digital_childrens-social-care-cpd" \ + /o:"dfe-digital" \ + /d:sonar.qualitygate.wait=true \ + /d:sonar.cs.vscoveragexml.reportsPaths=coverage.xml \ + /d:sonar.exclusions="**/*.css,**/*.scss,**/Models/*,**/Program.cs,**/WebApplicationBuilderExtensions.cs,**/GraphQL/Queries/*,**/Contentful-Schema/migrations/*.cjs" \ + /d:sonar.test.exclusions="Childrens-Social-Care-CPD-Tests/**/*" \ + /d:sonar.token="${{ secrets.SONAR_TOKEN }}" \ + /d:sonar.host.url="https://sonarcloud.io" + dotnet build --no-incremental + dotnet-coverage collect --settings dotnet-cover-config.xml -f xml -o coverage.xml "dotnet test" + dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" diff --git a/Childrens-Social-Care-CPD-Tests/Contentful/EntityResolverTests.cs b/Childrens-Social-Care-CPD-Tests/Contentful/EntityResolverTests.cs index a7486e1e..39372e58 100644 --- a/Childrens-Social-Care-CPD-Tests/Contentful/EntityResolverTests.cs +++ b/Childrens-Social-Care-CPD-Tests/Contentful/EntityResolverTests.cs @@ -25,6 +25,7 @@ public class EntityResolverTests [TestCase("contentSeparator", typeof(ContentSeparator))] [TestCase("detailedPathway", typeof(DetailedPathway))] [TestCase("detailedRole", typeof(DetailedRole))] + [TestCase("details", typeof(Details))] [TestCase("feedback", typeof(Feedback))] [TestCase("heroBanner", typeof(HeroBanner))] [TestCase("imageCard", typeof(ImageCard))] diff --git a/Childrens-Social-Care-CPD-Tests/Contentful/PartialsFactoryTests.cs b/Childrens-Social-Care-CPD-Tests/Contentful/PartialsFactoryTests.cs index 4387a32d..22f2274d 100644 --- a/Childrens-Social-Care-CPD-Tests/Contentful/PartialsFactoryTests.cs +++ b/Childrens-Social-Care-CPD-Tests/Contentful/PartialsFactoryTests.cs @@ -24,6 +24,7 @@ public partial class PartialsFactoryTests new object[] { new ContentsAnchor(), "_ContentsAnchor" }, new object[] { new DetailedPathway(), "_DetailedPathway" }, new object[] { new DetailedRole(), "_DetailedRole" }, + new object[] { new Details(), "_Details" }, new object[] { new Feedback(), "_Feedback" }, new object[] { new HeroBanner(), string.Empty }, new object[] { new LinkCard(), "_LinkCard" }, diff --git a/Childrens-Social-Care-CPD/Contentful/EntityResolver.cs b/Childrens-Social-Care-CPD/Contentful/EntityResolver.cs index 2f5fab1c..084d1019 100644 --- a/Childrens-Social-Care-CPD/Contentful/EntityResolver.cs +++ b/Childrens-Social-Care-CPD/Contentful/EntityResolver.cs @@ -28,6 +28,7 @@ public Type Resolve(string contentTypeId) "contentSeparator" => typeof(ContentSeparator), "detailedPathway" => typeof(DetailedPathway), "detailedRole" => typeof(DetailedRole), + "details" => typeof(Details), "feedback" => typeof(Feedback), "heroBanner" => typeof(HeroBanner), "imageCard" => typeof(ImageCard), diff --git a/Childrens-Social-Care-CPD/Contentful/Models/Details.cs b/Childrens-Social-Care-CPD/Contentful/Models/Details.cs new file mode 100644 index 00000000..f899df5f --- /dev/null +++ b/Childrens-Social-Care-CPD/Contentful/Models/Details.cs @@ -0,0 +1,9 @@ +using Contentful.Core.Models; + +namespace Childrens_Social_Care_CPD.Contentful.Models; + +public class Details : IContent +{ + public string SummaryText { get; set; } + public Document DetailsText { get; set; } +} diff --git a/Childrens-Social-Care-CPD/Contentful/PartialsFactory.cs b/Childrens-Social-Care-CPD/Contentful/PartialsFactory.cs index 73669f0d..906bf0bd 100644 --- a/Childrens-Social-Care-CPD/Contentful/PartialsFactory.cs +++ b/Childrens-Social-Care-CPD/Contentful/PartialsFactory.cs @@ -27,6 +27,7 @@ public static string GetPartialFor(IContent item) ContentsAnchor => "_ContentsAnchor", DetailedRole => "_DetailedRole", DetailedPathway => "_DetailedPathway", + Details => "_Details", Feedback => "_Feedback", HeroBanner => string.Empty,// skip - handled in specific layout section ImageCard => "_ImageCard", diff --git a/Childrens-Social-Care-CPD/Views/Shared/_AudioResource.cshtml b/Childrens-Social-Care-CPD/Views/Shared/_AudioResource.cshtml index 1e8ab5d3..1d1513bb 100644 --- a/Childrens-Social-Care-CPD/Views/Shared/_AudioResource.cshtml +++ b/Childrens-Social-Care-CPD/Views/Shared/_AudioResource.cshtml @@ -14,7 +14,8 @@ @if (Model.Transcript.Content != null) {
- View transcript + View transcript +
@{ await Html.RenderPartialAsync("_RichText", Model.Transcript); @@ -23,5 +24,4 @@
} - - + \ No newline at end of file diff --git a/Childrens-Social-Care-CPD/Views/Shared/_Details.cshtml b/Childrens-Social-Care-CPD/Views/Shared/_Details.cshtml new file mode 100644 index 00000000..4de3522a --- /dev/null +++ b/Childrens-Social-Care-CPD/Views/Shared/_Details.cshtml @@ -0,0 +1,16 @@ +@using Childrens_Social_Care_CPD.Contentful; +@using Childrens_Social_Care_CPD.Contentful.Models; +@using Childrens_Social_Care_CPD.Contentful.Renderers; + +@model Details + +
+ + + @Model.SummaryText + + +
+ +
+
\ No newline at end of file diff --git a/Contentful-Schema/migrations/0008-details-component.cjs b/Contentful-Schema/migrations/0008-details-component.cjs new file mode 100644 index 00000000..ffe49ce7 --- /dev/null +++ b/Contentful-Schema/migrations/0008-details-component.cjs @@ -0,0 +1,99 @@ +module.exports = async function (migration, { makeRequest }) { + const details = migration + .createContentType("details") + .name("Details") + .description( + "Allows users to view more detailed information if they need it, as per the GDS component of the same name." + ) + .displayField("summaryText"); + + details + .createField("summaryText") + .name("Summary text") + .type("Symbol") + .localized(false) + .required(true) + .validations([]) + .disabled(false) + .omitted(false); + + details + .createField("detailsText") + .name("Details text") + .type("RichText") + .localized(false) + .required(true) + .validations([ + { + enabledMarks: [ + "bold", + "italic", + "underline", + "code", + "superscript", + "subscript", + ], + message: + "Only bold, italic, underline, code, superscript, and subscript marks are allowed", + }, + { + enabledNodeTypes: [ + "heading-1", + "heading-2", + "heading-3", + "heading-4", + "heading-5", + "heading-6", + "ordered-list", + "unordered-list", + "hr", + "blockquote", + "embedded-entry-block", + "embedded-asset-block", + "table", + "asset-hyperlink", + "embedded-entry-inline", + "entry-hyperlink", + "hyperlink", + ], + + message: + "Only heading 1, heading 2, heading 3, heading 4, heading 5, heading 6, ordered list, unordered list, horizontal rule, quote, block entry, asset, table, link to asset, inline entry, link to entry, and link to Url nodes are allowed", + }, + { + nodes: {}, + }, + ]) + .disabled(false) + .omitted(false); + + details.changeFieldControl("summaryText", "builtin", "singleLine", {}); + details.changeFieldControl("detailsText", "builtin", "richTextEditor", {}); + + const contentTypeId = "content", + linkingFieldId = "items", + detailsTypeId = "details"; + + const response = await makeRequest({ + method: "GET", + url: `/content_types?sys.id[in]=${contentTypeId}`, + }); + + const validations = response.items[0].fields + .filter((field) => field.id == linkingFieldId)[0] + .items.validations.map((rule) => { + if ( + rule.linkContentType && + !rule.linkContentType.includes(detailsTypeId) + ) { + rule.linkContentType.push(detailsTypeId); + } + return rule; + }); + + migration.editContentType(contentTypeId).editField(linkingFieldId).items({ + type: "Link", + linkType: "Entry", + validations: validations, + }); +}; diff --git a/Contentful-Schema/migrations/manifest.txt b/Contentful-Schema/migrations/manifest.txt index a975447b..430a1228 100644 --- a/Contentful-Schema/migrations/manifest.txt +++ b/Contentful-Schema/migrations/manifest.txt @@ -4,4 +4,5 @@ 0004-back-to-top-component.cjs 0005-create-employer-standards-page-category.cjs 0006-accordion-component.cjs -0007-add-header-field-to-navigation-menu.cjs \ No newline at end of file +0007-add-header-field-to-navigation-menu.cjs +0008-details-component.cjs \ No newline at end of file diff --git a/Contentful-Schema/migrations/migrations.tar.gz b/Contentful-Schema/migrations/migrations.tar.gz index 1a6f72cf..5cd70594 100644 Binary files a/Contentful-Schema/migrations/migrations.tar.gz and b/Contentful-Schema/migrations/migrations.tar.gz differ diff --git a/README.md b/README.md index bef557d6..900de7c5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # childrens-social-care-cpd -This repository contains the code needed to host the Social Workforce career progression service. This service helps workers in this profession to easily find information about the pathways to further their career - +This repository contains the code needed to host the Social Workforce career progression service. This service helps workers in this profession to easily find information about the pathways to further their career. + ## Live examples The live site can be found [here](https://www.develop-child-family-social-work-career.education.gov.uk) @@ -20,25 +20,25 @@ You will need a Contentful CMS instance with the correct Data models. The CMS is The following environment variables are to be configured -| Variable | description | -| --- | --- | -| CPD_GOOGLEANALYTICSTAG | The google analytics API (optional) | -| CPD_CONTENTFUL_ENVIRONMENT | The contentful environment being consumed | -| CPD_SPACE_ID | The contentful space id | -| CPD_DELIVERY_KEY | The live content for the environment set | -| CPD_PREVIEW_KEY | The preview content for the environment set | -| CPD_AZURE_ENVIRONMENT | The environment for the system | -| CPD_INSTRUMENTATION_CONNECTIONSTRING | Application insights connection string | -| CPD_FEATURE_POLLING_INTERVAL | Polling interval for feature configuration udpates | -| CPD_SEARCH_CLIENT_API_KEY | Client API Key for the search service | -| CPD_SEARCH_ENDPOINT | Search service endpoint | -| CPD_SEARCH_INDEX_NAME | The Index name to query against | +| Variable | description | +| ------------------------------------ | -------------------------------------------------- | +| CPD_GOOGLEANALYTICSTAG | The google analytics API (optional) | +| CPD_CONTENTFUL_ENVIRONMENT | The contentful environment being consumed | +| CPD_SPACE_ID | The contentful space id | +| CPD_DELIVERY_KEY | The live content for the environment set | +| CPD_PREVIEW_KEY | The preview content for the environment set | +| CPD_AZURE_ENVIRONMENT | The environment for the system | +| CPD_INSTRUMENTATION_CONNECTIONSTRING | Application insights connection string | +| CPD_FEATURE_POLLING_INTERVAL | Polling interval for feature configuration udpates | +| CPD_SEARCH_CLIENT_API_KEY | Client API Key for the search service | +| CPD_SEARCH_ENDPOINT | Search service endpoint | +| CPD_SEARCH_INDEX_NAME | The Index name to query against | In order to run the application locally, you can either open the solution, build and run in the IDE of your choice or there is a [docker-compose](~/docker-compose.yml) file that allows for local running of the app. In order to run the app with the compose file you will need `docker` and `docker-compose` installed and run the following command in the root directory. -`docker-compose up` +`docker-compose up` ### Running the test suite @@ -53,9 +53,11 @@ followed by Cypress testing is also included. ### Compiling the Sass -The Sass is compiled using the ```compile-sass.ps1``` script in the main project. The commands can also be used in *nix environments. + +The Sass is compiled using the `compile-sass.ps1` script in the main project. The commands can also be used in \*nix environments. ## Releases + Information around the Github Release process can be found in the [release documentation](./docs/RELEASE.md). ## Contentful @@ -63,4 +65,5 @@ Information around the Github Release process can be found in the [release docum More information on Contentful can be found [here](https://www.contentful.com/) --- + Copyright (c) 2023 Crown Copyright (Department for Education) diff --git a/migrations.tar.gz b/migrations.tar.gz new file mode 100644 index 00000000..872c0155 Binary files /dev/null and b/migrations.tar.gz differ