diff --git a/src/GovUk.Education.ExploreEducationStatistics.Admin/Controllers/Api/DataSetFileGeographicLevelsMigrationController.cs b/src/GovUk.Education.ExploreEducationStatistics.Admin/Controllers/Api/DataSetFileGeographicLevelsMigrationController.cs new file mode 100644 index 00000000000..b4ceec2eb98 --- /dev/null +++ b/src/GovUk.Education.ExploreEducationStatistics.Admin/Controllers/Api/DataSetFileGeographicLevelsMigrationController.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using GovUk.Education.ExploreEducationStatistics.Admin.Models; +using GovUk.Education.ExploreEducationStatistics.Common.Extensions; +using GovUk.Education.ExploreEducationStatistics.Common.Model; +using GovUk.Education.ExploreEducationStatistics.Content.Model; +using GovUk.Education.ExploreEducationStatistics.Content.Model.Database; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace GovUk.Education.ExploreEducationStatistics.Admin.Controllers.Api; + +[Route("api")] +[ApiController] +[Authorize(Roles = GlobalRoles.RoleNames.BauUser)] +public class DataSetFileMetaMigrationController(ContentDbContext contentDbContext) : ControllerBase +{ + public class MigrationResult + { + public bool IsDryRun; + public int Processed; + public string Errors; + } + + [HttpPut("bau/migrate-datasetfile-geographiclevels")] + public async Task DataSetFileVersionGeographicLevelsMigration( + [FromQuery] bool isDryRun = true, + [FromQuery] int? num = null, + CancellationToken cancellationToken = default) + { + var queryable = contentDbContext.Files + .Where(f => f.Type == FileType.Data + && f.DataSetFileVersionGeographicLevels.Count == 0); + + if (num != null) + { + queryable = queryable.Take(num.Value); + } + + var files = queryable.ToList(); + + var numProcessed = 0; + List errors = []; + + foreach (var file in files) + { + var meta = file.DataSetFileMeta; + + if (meta == null) + { + errors.Add($"No DataSetFileMeta found for File {file.Id}"); + continue; + } + + var dataSetFileVersionGeographicLevels = meta!.GeographicLevels + .Distinct() + .Select(gl => new DataSetFileVersionGeographicLevel + { + DataSetFileVersionId = file.Id, + GeographicLevel = gl, + }) + .ToList(); + + contentDbContext.DataSetFileVersionGeographicLevels.AddRange( + dataSetFileVersionGeographicLevels); + + numProcessed++; + } + + if (!isDryRun) + { + await contentDbContext.SaveChangesAsync(cancellationToken); + } + + return new MigrationResult + { + IsDryRun = isDryRun, + Processed = numProcessed, + Errors = errors.IsNullOrEmpty() ? "No errors" : errors.JoinToString("\n"), + }; + } + +} diff --git a/src/GovUk.Education.ExploreEducationStatistics.Admin/Migrations/ContentMigrations/20241219093820_EES5738_CreateDataSetFileGeographicLevelsTable.Designer.cs b/src/GovUk.Education.ExploreEducationStatistics.Admin/Migrations/ContentMigrations/20241219093820_EES5738_CreateDataSetFileGeographicLevelsTable.Designer.cs new file mode 100644 index 00000000000..739bb0e01e7 --- /dev/null +++ b/src/GovUk.Education.ExploreEducationStatistics.Admin/Migrations/ContentMigrations/20241219093820_EES5738_CreateDataSetFileGeographicLevelsTable.Designer.cs @@ -0,0 +1,2273 @@ +// +using System; +using GovUk.Education.ExploreEducationStatistics.Content.Model.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace GovUk.Education.ExploreEducationStatistics.Admin.Migrations.ContentMigrations +{ + [DbContext(typeof(ContentDbContext))] + [Migration("20241219093820_EES5738_CreateDataSetFileGeographicLevelsTable")] + partial class EES5738_CreateDataSetFileGeographicLevelsTable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Common.Model.Contact", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ContactName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ContactTelNo") + .HasColumnType("nvarchar(max)"); + + b.Property("TeamEmail") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("TeamName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Contacts"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Common.Model.FreeTextRank", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Rank") + .HasColumnType("int"); + + b.ToTable((string)null); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.Comment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ContentBlockId") + .HasColumnType("uniqueidentifier"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier"); + + b.Property("LegacyCreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Resolved") + .HasColumnType("datetime2"); + + b.Property("ResolvedById") + .HasColumnType("uniqueidentifier"); + + b.Property("Updated") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("ContentBlockId"); + + b.HasIndex("CreatedById"); + + b.HasIndex("ResolvedById"); + + b.ToTable("Comment"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.ContentBlock", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ContentSectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("Locked") + .HasColumnType("datetime2"); + + b.Property("LockedById") + .IsConcurrencyToken() + .HasColumnType("uniqueidentifier"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("ReleaseVersionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(25) + .HasColumnType("nvarchar(25)"); + + b.Property("Updated") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("ContentSectionId"); + + b.HasIndex("LockedById"); + + b.HasIndex("ReleaseVersionId"); + + b.HasIndex("Type"); + + b.ToTable("ContentBlock", (string)null); + + b.HasDiscriminator("Type").HasValue("ContentBlock"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.ContentSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Caption") + .HasColumnType("nvarchar(max)"); + + b.Property("Heading") + .HasColumnType("nvarchar(max)"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("ReleaseVersionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(25) + .HasColumnType("nvarchar(25)"); + + b.HasKey("Id"); + + b.HasIndex("ReleaseVersionId"); + + b.HasIndex("Type"); + + b.ToTable("ContentSections"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.DataBlockParent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("LatestDraftVersionId") + .HasColumnType("uniqueidentifier"); + + b.Property("LatestPublishedVersionId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("LatestDraftVersionId") + .IsUnique() + .HasFilter("[LatestDraftVersionId] IS NOT NULL"); + + b.HasIndex("LatestPublishedVersionId") + .IsUnique() + .HasFilter("[LatestPublishedVersionId] IS NOT NULL"); + + b.ToTable("DataBlocks", (string)null); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.DataBlockVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ContentBlockId") + .HasColumnType("uniqueidentifier"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("DataBlockParentId") + .HasColumnType("uniqueidentifier") + .HasColumnName("DataBlockId"); + + b.Property("Published") + .HasColumnType("datetime2"); + + b.Property("ReleaseVersionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Updated") + .HasColumnType("datetime2"); + + b.Property("Version") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ContentBlockId"); + + b.HasIndex("DataBlockParentId"); + + b.HasIndex("ReleaseVersionId"); + + b.ToTable("DataBlockVersions", (string)null); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.DataImport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("ExpectedImportedRows") + .HasColumnType("int"); + + b.Property("FileId") + .HasColumnType("uniqueidentifier"); + + b.Property("GeographicLevels") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ImportedRows") + .HasColumnType("int"); + + b.Property("LastProcessedRowIndex") + .HasColumnType("int"); + + b.Property("MetaFileId") + .HasColumnType("uniqueidentifier"); + + b.Property("StagePercentageComplete") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SubjectId") + .HasColumnType("uniqueidentifier"); + + b.Property("TotalRows") + .HasColumnType("int"); + + b.Property("ZipFileId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("FileId") + .IsUnique(); + + SqlServerIndexBuilderExtensions.IncludeProperties(b.HasIndex("FileId"), new[] { "Status" }); + + b.HasIndex("MetaFileId") + .IsUnique(); + + b.HasIndex("ZipFileId"); + + b.ToTable("DataImports"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.DataImportError", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("DataImportId") + .HasColumnType("uniqueidentifier"); + + b.Property("Message") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DataImportId"); + + b.ToTable("DataImportErrors"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.DataSetFileVersionGeographicLevel", b => + { + b.Property("DataSetFileVersionId") + .HasColumnType("uniqueidentifier"); + + b.Property("GeographicLevel") + .HasMaxLength(6) + .HasColumnType("nvarchar(6)"); + + b.HasKey("DataSetFileVersionId", "GeographicLevel"); + + b.ToTable("DataSetFileVersionGeographicLevels"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.EmbedBlock", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Updated") + .HasColumnType("datetime2"); + + b.Property("Url") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("EmbedBlocks"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.FeaturedTable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier"); + + b.Property("DataBlockId") + .HasColumnType("uniqueidentifier"); + + b.Property("DataBlockParentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("ReleaseVersionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Updated") + .HasColumnType("datetime2"); + + b.Property("UpdatedById") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DataBlockId") + .IsUnique(); + + b.HasIndex("DataBlockParentId"); + + b.HasIndex("ReleaseVersionId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("FeaturedTables"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.File", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ContentLength") + .HasColumnType("bigint"); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier"); + + b.Property("DataSetFileId") + .HasColumnType("uniqueidentifier"); + + b.Property("DataSetFileMeta") + .HasColumnType("nvarchar(max)"); + + b.Property("DataSetFileVersion") + .HasColumnType("int"); + + b.Property("Filename") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ReplacedById") + .HasColumnType("uniqueidentifier"); + + b.Property("ReplacingId") + .HasColumnType("uniqueidentifier"); + + b.Property("RootPath") + .HasColumnType("uniqueidentifier"); + + b.Property("SourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("SubjectId") + .HasColumnType("uniqueidentifier"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(25) + .HasColumnType("nvarchar(25)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("ReplacedById") + .IsUnique() + .HasFilter("[ReplacedById] IS NOT NULL"); + + b.HasIndex("ReplacingId") + .IsUnique() + .HasFilter("[ReplacingId] IS NOT NULL"); + + b.HasIndex("SourceId"); + + b.HasIndex("Type"); + + b.ToTable("Files"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.GlossaryEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Body") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.ToTable("GlossaryEntries"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.KeyStatistic", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier"); + + b.Property("GuidanceText") + .HasColumnType("nvarchar(max)"); + + b.Property("GuidanceTitle") + .HasColumnType("nvarchar(max)"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("ReleaseVersionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Trend") + .HasColumnType("nvarchar(max)"); + + b.Property("Updated") + .HasColumnType("datetime2"); + + b.Property("UpdatedById") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("ReleaseVersionId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("KeyStatistics"); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.Methodology", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("LatestPublishedVersionId") + .HasColumnType("uniqueidentifier"); + + b.Property("OwningPublicationSlug") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("OwningPublicationTitle") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("LatestPublishedVersionId") + .IsUnique() + .HasFilter("[LatestPublishedVersionId] IS NOT NULL"); + + b.ToTable("Methodologies"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.MethodologyFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("FileId") + .HasColumnType("uniqueidentifier"); + + b.Property("MethodologyVersionId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("FileId"); + + b.HasIndex("MethodologyVersionId"); + + b.ToTable("MethodologyFiles"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.MethodologyNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier"); + + b.Property("DisplayDate") + .HasColumnType("datetime2"); + + b.Property("MethodologyVersionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Updated") + .HasColumnType("datetime2"); + + b.Property("UpdatedById") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("MethodologyVersionId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("MethodologyNotes"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.MethodologyRedirect", b => + { + b.Property("MethodologyVersionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Slug") + .HasColumnType("nvarchar(450)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.HasKey("MethodologyVersionId", "Slug"); + + b.ToTable("MethodologyRedirects"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.MethodologyStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApprovalStatus") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier"); + + b.Property("InternalReleaseNote") + .HasColumnType("nvarchar(max)"); + + b.Property("MethodologyVersionId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("MethodologyVersionId"); + + b.ToTable("MethodologyStatus"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.MethodologyVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AlternativeSlug") + .HasColumnType("nvarchar(max)"); + + b.Property("AlternativeTitle") + .HasColumnType("nvarchar(max)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier"); + + b.Property("MethodologyId") + .HasColumnType("uniqueidentifier"); + + b.Property("PreviousVersionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Published") + .HasColumnType("datetime2"); + + b.Property("PublishingStrategy") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ScheduledWithReleaseVersionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Updated") + .HasColumnType("datetime2"); + + b.Property("Version") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("MethodologyId"); + + b.HasIndex("PreviousVersionId"); + + b.HasIndex("ScheduledWithReleaseVersionId"); + + b.ToTable("MethodologyVersions"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.MethodologyVersionContent", b => + { + b.Property("MethodologyVersionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Annexes") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("MethodologyVersionId"); + + b.ToTable("MethodologyVersions", (string)null); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.Permalink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("DataSetTitle") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("MigratedFromLegacy") + .HasColumnType("bit"); + + b.Property("PublicationTitle") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseVersionId") + .HasColumnType("uniqueidentifier"); + + b.Property("SubjectId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ReleaseVersionId"); + + b.HasIndex("SubjectId"); + + b.ToTable("Permalinks"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.Publication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ContactId") + .HasColumnType("uniqueidentifier"); + + b.Property("LatestPublishedReleaseVersionId") + .HasColumnType("uniqueidentifier"); + + b.Property("ReleaseSeries") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(160) + .HasColumnType("nvarchar(160)"); + + b.Property("SupersededById") + .HasColumnType("uniqueidentifier"); + + b.Property("ThemeId") + .HasColumnType("uniqueidentifier"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Updated") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("ContactId"); + + b.HasIndex("LatestPublishedReleaseVersionId") + .IsUnique() + .HasFilter("[LatestPublishedReleaseVersionId] IS NOT NULL"); + + b.HasIndex("SupersededById"); + + b.HasIndex("ThemeId"); + + b.ToTable("Publications"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.PublicationMethodology", b => + { + b.Property("PublicationId") + .HasColumnType("uniqueidentifier"); + + b.Property("MethodologyId") + .HasColumnType("uniqueidentifier"); + + b.Property("Owner") + .HasColumnType("bit"); + + b.HasKey("PublicationId", "MethodologyId"); + + b.HasIndex("MethodologyId"); + + b.ToTable("PublicationMethodologies"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.PublicationRedirect", b => + { + b.Property("PublicationId") + .HasColumnType("uniqueidentifier"); + + b.Property("Slug") + .HasColumnType("nvarchar(450)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.HasKey("PublicationId", "Slug"); + + b.ToTable("PublicationRedirects"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.Release", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("Label") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("PublicationId") + .HasColumnType("uniqueidentifier"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(81) + .HasColumnType("nvarchar(81)"); + + b.Property("TimePeriodCoverage") + .IsRequired() + .HasMaxLength(5) + .HasColumnType("nvarchar(5)"); + + b.Property("Updated") + .HasColumnType("datetime2"); + + b.Property("Year") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("PublicationId", "Year", "TimePeriodCoverage", "Label") + .IsUnique(); + + b.ToTable("Releases"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.ReleaseFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("FileId") + .HasColumnType("uniqueidentifier"); + + b.Property("FilterSequence") + .HasColumnType("nvarchar(max)"); + + b.Property("IndicatorSequence") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("PublicApiDataSetId") + .HasColumnType("uniqueidentifier"); + + b.Property("PublicApiDataSetVersion") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Published") + .HasColumnType("datetime2"); + + b.Property("ReleaseVersionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Summary") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("FileId"); + + b.HasIndex("ReleaseVersionId", "FileId") + .IsUnique(); + + b.HasIndex("ReleaseVersionId", "PublicApiDataSetId", "PublicApiDataSetVersion") + .IsUnique() + .HasFilter("[PublicApiDataSetId] IS NOT NULL AND [PublicApiDataSetVersion] IS NOT NULL"); + + b.ToTable("ReleaseFiles"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.ReleaseRedirect", b => + { + b.Property("ReleaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Slug") + .HasColumnType("nvarchar(450)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.HasKey("ReleaseId", "Slug"); + + b.ToTable("ReleaseRedirects"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.ReleaseStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApprovalStatus") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier"); + + b.Property("InternalReleaseNote") + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseVersionId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("ReleaseVersionId"); + + b.ToTable("ReleaseStatus"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.ReleaseVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApprovalStatus") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier"); + + b.Property("DataGuidance") + .HasColumnType("nvarchar(max)"); + + b.Property("NextReleaseDate") + .HasColumnType("nvarchar(max)"); + + b.Property("NotifiedOn") + .HasColumnType("datetime2"); + + b.Property("NotifySubscribers") + .HasColumnType("bit"); + + b.Property("PreReleaseAccessList") + .HasColumnType("nvarchar(max)"); + + b.Property("PreviousVersionId") + .HasColumnType("uniqueidentifier"); + + b.Property("PublicationId") + .HasColumnType("uniqueidentifier"); + + b.Property("PublishScheduled") + .HasColumnType("datetime2"); + + b.Property("Published") + .HasColumnType("datetime2"); + + b.Property("RelatedInformation") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("ReleaseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Slug") + .HasColumnType("nvarchar(max)"); + + b.Property("SoftDeleted") + .HasColumnType("bit"); + + b.Property("TimePeriodCoverage") + .IsRequired() + .HasMaxLength(6) + .HasColumnType("nvarchar(6)"); + + b.Property("Type") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("UpdatePublishedDate") + .HasColumnType("bit"); + + b.Property("Version") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("PublicationId"); + + b.HasIndex("ReleaseId"); + + b.HasIndex("Type"); + + b.HasIndex("PreviousVersionId", "Version"); + + b.ToTable("ReleaseVersions"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.Theme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Slug") + .HasColumnType("nvarchar(max)"); + + b.Property("Summary") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Themes"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.Update", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier"); + + b.Property("On") + .HasColumnType("datetime2"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseVersionId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("ReleaseVersionId"); + + b.ToTable("Update"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("FirstName") + .HasColumnType("nvarchar(max)"); + + b.Property("LastName") + .HasColumnType("nvarchar(max)"); + + b.Property("SoftDeleted") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("DeletedById"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.UserPublicationInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PublicationId") + .HasColumnType("uniqueidentifier"); + + b.Property("Role") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("PublicationId"); + + b.ToTable("UserPublicationInvites"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.UserPublicationRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier"); + + b.Property("Deleted") + .HasColumnType("datetime2"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier"); + + b.Property("PublicationId") + .HasColumnType("uniqueidentifier"); + + b.Property("Role") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("PublicationId"); + + b.HasIndex("UserId"); + + b.ToTable("UserPublicationRoles"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.UserReleaseInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EmailSent") + .HasColumnType("bit"); + + b.Property("ReleaseVersionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Role") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SoftDeleted") + .HasColumnType("bit"); + + b.Property("Updated") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("ReleaseVersionId"); + + b.ToTable("UserReleaseInvites"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.UserReleaseRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier"); + + b.Property("Deleted") + .HasColumnType("datetime2"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier"); + + b.Property("ReleaseVersionId") + .HasColumnType("uniqueidentifier"); + + b.Property("Role") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SoftDeleted") + .HasColumnType("bit"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("ReleaseVersionId"); + + b.HasIndex("UserId"); + + b.ToTable("UserReleaseRoles"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.DataBlock", b => + { + b.HasBaseType("GovUk.Education.ExploreEducationStatistics.Content.Model.ContentBlock"); + + b.Property("Charts") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("DataBlock_Charts"); + + b.Property("Heading") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("DataBlock_Heading"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Query") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("DataBlock_Query"); + + b.Property("Source") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Table") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("DataBlock_Table"); + + b.HasDiscriminator().HasValue("DataBlock"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.EmbedBlockLink", b => + { + b.HasBaseType("GovUk.Education.ExploreEducationStatistics.Content.Model.ContentBlock"); + + b.Property("EmbedBlockId") + .HasColumnType("uniqueidentifier") + .HasColumnName("EmbedBlockId"); + + b.HasIndex("EmbedBlockId") + .IsUnique() + .HasFilter("[EmbedBlockId] IS NOT NULL"); + + b.HasDiscriminator().HasValue("EmbedBlockLink"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.HtmlBlock", b => + { + b.HasBaseType("GovUk.Education.ExploreEducationStatistics.Content.Model.ContentBlock"); + + b.Property("Body") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("nvarchar(max)") + .HasColumnName("Body"); + + b.HasDiscriminator().HasValue("HtmlBlock"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.MarkDownBlock", b => + { + b.HasBaseType("GovUk.Education.ExploreEducationStatistics.Content.Model.ContentBlock"); + + b.Property("Body") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("nvarchar(max)") + .HasColumnName("Body"); + + b.HasDiscriminator().HasValue("MarkDownBlock"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.KeyStatisticDataBlock", b => + { + b.HasBaseType("GovUk.Education.ExploreEducationStatistics.Content.Model.KeyStatistic"); + + b.Property("DataBlockId") + .HasColumnType("uniqueidentifier"); + + b.Property("DataBlockParentId") + .HasColumnType("uniqueidentifier"); + + b.HasIndex("DataBlockId"); + + b.HasIndex("DataBlockParentId"); + + b.ToTable("KeyStatisticsDataBlock", (string)null); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.KeyStatisticText", b => + { + b.HasBaseType("GovUk.Education.ExploreEducationStatistics.Content.Model.KeyStatistic"); + + b.Property("Statistic") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.ToTable("KeyStatisticsText", (string)null); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.Comment", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.ContentBlock", "ContentBlock") + .WithMany("Comments") + .HasForeignKey("ContentBlockId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.User", "ResolvedBy") + .WithMany() + .HasForeignKey("ResolvedById"); + + b.Navigation("ContentBlock"); + + b.Navigation("CreatedBy"); + + b.Navigation("ResolvedBy"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.ContentBlock", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.ContentSection", "ContentSection") + .WithMany("Content") + .HasForeignKey("ContentSectionId"); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.User", "LockedBy") + .WithMany() + .HasForeignKey("LockedById"); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.ReleaseVersion", "ReleaseVersion") + .WithMany() + .HasForeignKey("ReleaseVersionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ContentSection"); + + b.Navigation("LockedBy"); + + b.Navigation("ReleaseVersion"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.ContentSection", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.ReleaseVersion", "ReleaseVersion") + .WithMany("Content") + .HasForeignKey("ReleaseVersionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ReleaseVersion"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.DataBlockParent", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.DataBlockVersion", "LatestDraftVersion") + .WithOne() + .HasForeignKey("GovUk.Education.ExploreEducationStatistics.Content.Model.DataBlockParent", "LatestDraftVersionId"); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.DataBlockVersion", "LatestPublishedVersion") + .WithOne() + .HasForeignKey("GovUk.Education.ExploreEducationStatistics.Content.Model.DataBlockParent", "LatestPublishedVersionId"); + + b.Navigation("LatestDraftVersion"); + + b.Navigation("LatestPublishedVersion"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.DataBlockVersion", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.DataBlock", "ContentBlock") + .WithMany() + .HasForeignKey("ContentBlockId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.DataBlockParent", "DataBlockParent") + .WithMany() + .HasForeignKey("DataBlockParentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.ReleaseVersion", "ReleaseVersion") + .WithMany("DataBlockVersions") + .HasForeignKey("ReleaseVersionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ContentBlock"); + + b.Navigation("DataBlockParent"); + + b.Navigation("ReleaseVersion"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.DataImport", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.File", "File") + .WithOne() + .HasForeignKey("GovUk.Education.ExploreEducationStatistics.Content.Model.DataImport", "FileId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.File", "MetaFile") + .WithOne() + .HasForeignKey("GovUk.Education.ExploreEducationStatistics.Content.Model.DataImport", "MetaFileId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.File", "ZipFile") + .WithMany() + .HasForeignKey("ZipFileId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("File"); + + b.Navigation("MetaFile"); + + b.Navigation("ZipFile"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.DataImportError", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.DataImport", "DataImport") + .WithMany("Errors") + .HasForeignKey("DataImportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DataImport"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.DataSetFileVersionGeographicLevel", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.File", "DataSetFileVersion") + .WithMany("DataSetFileVersionGeographicLevels") + .HasForeignKey("DataSetFileVersionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DataSetFileVersion"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.FeaturedTable", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.DataBlock", "DataBlock") + .WithOne() + .HasForeignKey("GovUk.Education.ExploreEducationStatistics.Content.Model.FeaturedTable", "DataBlockId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.DataBlockParent", "DataBlockParent") + .WithMany() + .HasForeignKey("DataBlockParentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.ReleaseVersion", "ReleaseVersion") + .WithMany("FeaturedTables") + .HasForeignKey("ReleaseVersionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("DataBlock"); + + b.Navigation("DataBlockParent"); + + b.Navigation("ReleaseVersion"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.File", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.File", "ReplacedBy") + .WithOne() + .HasForeignKey("GovUk.Education.ExploreEducationStatistics.Content.Model.File", "ReplacedById"); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.File", "Replacing") + .WithOne() + .HasForeignKey("GovUk.Education.ExploreEducationStatistics.Content.Model.File", "ReplacingId"); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.File", "Source") + .WithMany() + .HasForeignKey("SourceId"); + + b.Navigation("CreatedBy"); + + b.Navigation("ReplacedBy"); + + b.Navigation("Replacing"); + + b.Navigation("Source"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.GlossaryEntry", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("CreatedBy"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.KeyStatistic", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.ReleaseVersion", "ReleaseVersion") + .WithMany("KeyStatistics") + .HasForeignKey("ReleaseVersionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("ReleaseVersion"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.Methodology", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.MethodologyVersion", "LatestPublishedVersion") + .WithOne() + .HasForeignKey("GovUk.Education.ExploreEducationStatistics.Content.Model.Methodology", "LatestPublishedVersionId"); + + b.Navigation("LatestPublishedVersion"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.MethodologyFile", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.File", "File") + .WithMany() + .HasForeignKey("FileId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.MethodologyVersion", "MethodologyVersion") + .WithMany() + .HasForeignKey("MethodologyVersionId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("File"); + + b.Navigation("MethodologyVersion"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.MethodologyNote", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.MethodologyVersion", "MethodologyVersion") + .WithMany("Notes") + .HasForeignKey("MethodologyVersionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById") + .OnDelete(DeleteBehavior.NoAction); + + b.Navigation("CreatedBy"); + + b.Navigation("MethodologyVersion"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.MethodologyRedirect", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.MethodologyVersion", "MethodologyVersion") + .WithMany("MethodologyRedirects") + .HasForeignKey("MethodologyVersionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MethodologyVersion"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.MethodologyStatus", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.MethodologyVersion", "MethodologyVersion") + .WithMany() + .HasForeignKey("MethodologyVersionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CreatedBy"); + + b.Navigation("MethodologyVersion"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.MethodologyVersion", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.Methodology", "Methodology") + .WithMany("Versions") + .HasForeignKey("MethodologyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.MethodologyVersion", "PreviousVersion") + .WithMany() + .HasForeignKey("PreviousVersionId"); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.ReleaseVersion", "ScheduledWithReleaseVersion") + .WithMany() + .HasForeignKey("ScheduledWithReleaseVersionId"); + + b.Navigation("CreatedBy"); + + b.Navigation("Methodology"); + + b.Navigation("PreviousVersion"); + + b.Navigation("ScheduledWithReleaseVersion"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.MethodologyVersionContent", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.MethodologyVersion", null) + .WithOne("MethodologyContent") + .HasForeignKey("GovUk.Education.ExploreEducationStatistics.Content.Model.MethodologyVersionContent", "MethodologyVersionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.Publication", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Common.Model.Contact", "Contact") + .WithMany() + .HasForeignKey("ContactId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.ReleaseVersion", "LatestPublishedReleaseVersion") + .WithOne() + .HasForeignKey("GovUk.Education.ExploreEducationStatistics.Content.Model.Publication", "LatestPublishedReleaseVersionId"); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.Publication", "SupersededBy") + .WithMany() + .HasForeignKey("SupersededById"); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.Theme", "Theme") + .WithMany("Publications") + .HasForeignKey("ThemeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("GovUk.Education.ExploreEducationStatistics.Content.Model.ExternalMethodology", "ExternalMethodology", b1 => + { + b1.Property("PublicationId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.Property("Url") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.HasKey("PublicationId"); + + b1.ToTable("ExternalMethodology", (string)null); + + b1.WithOwner() + .HasForeignKey("PublicationId"); + }); + + b.Navigation("Contact"); + + b.Navigation("ExternalMethodology"); + + b.Navigation("LatestPublishedReleaseVersion"); + + b.Navigation("SupersededBy"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.PublicationMethodology", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.Methodology", "Methodology") + .WithMany("Publications") + .HasForeignKey("MethodologyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.Publication", "Publication") + .WithMany("Methodologies") + .HasForeignKey("PublicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Methodology"); + + b.Navigation("Publication"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.PublicationRedirect", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.Publication", "Publication") + .WithMany("PublicationRedirects") + .HasForeignKey("PublicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Publication"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.Release", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.Publication", "Publication") + .WithMany("Releases") + .HasForeignKey("PublicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Publication"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.ReleaseFile", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.File", "File") + .WithMany() + .HasForeignKey("FileId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.ReleaseVersion", "ReleaseVersion") + .WithMany() + .HasForeignKey("ReleaseVersionId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("File"); + + b.Navigation("ReleaseVersion"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.ReleaseRedirect", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.Release", "Release") + .WithMany("ReleaseRedirects") + .HasForeignKey("ReleaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Release"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.ReleaseStatus", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.ReleaseVersion", "ReleaseVersion") + .WithMany("ReleaseStatuses") + .HasForeignKey("ReleaseVersionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CreatedBy"); + + b.Navigation("ReleaseVersion"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.ReleaseVersion", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.ReleaseVersion", "PreviousVersion") + .WithMany() + .HasForeignKey("PreviousVersionId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.Publication", "Publication") + .WithMany("ReleaseVersions") + .HasForeignKey("PublicationId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.Release", "Release") + .WithMany("Versions") + .HasForeignKey("ReleaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CreatedBy"); + + b.Navigation("PreviousVersion"); + + b.Navigation("Publication"); + + b.Navigation("Release"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.Update", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.ReleaseVersion", "ReleaseVersion") + .WithMany("Updates") + .HasForeignKey("ReleaseVersionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CreatedBy"); + + b.Navigation("ReleaseVersion"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.User", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.User", "DeletedBy") + .WithMany() + .HasForeignKey("DeletedById"); + + b.Navigation("DeletedBy"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.UserPublicationInvite", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.Publication", "Publication") + .WithMany() + .HasForeignKey("PublicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CreatedBy"); + + b.Navigation("Publication"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.UserPublicationRole", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.User", "DeletedBy") + .WithMany() + .HasForeignKey("DeletedById"); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.Publication", "Publication") + .WithMany() + .HasForeignKey("PublicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("Publication"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.UserReleaseInvite", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.ReleaseVersion", "ReleaseVersion") + .WithMany() + .HasForeignKey("ReleaseVersionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CreatedBy"); + + b.Navigation("ReleaseVersion"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.UserReleaseRole", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.User", "DeletedBy") + .WithMany() + .HasForeignKey("DeletedById"); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.ReleaseVersion", "ReleaseVersion") + .WithMany() + .HasForeignKey("ReleaseVersionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("ReleaseVersion"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.EmbedBlockLink", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.EmbedBlock", "EmbedBlock") + .WithOne() + .HasForeignKey("GovUk.Education.ExploreEducationStatistics.Content.Model.EmbedBlockLink", "EmbedBlockId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EmbedBlock"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.KeyStatisticDataBlock", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.DataBlock", "DataBlock") + .WithMany() + .HasForeignKey("DataBlockId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.DataBlockParent", "DataBlockParent") + .WithMany() + .HasForeignKey("DataBlockParentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.KeyStatistic", null) + .WithOne() + .HasForeignKey("GovUk.Education.ExploreEducationStatistics.Content.Model.KeyStatisticDataBlock", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DataBlock"); + + b.Navigation("DataBlockParent"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.KeyStatisticText", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.KeyStatistic", null) + .WithOne() + .HasForeignKey("GovUk.Education.ExploreEducationStatistics.Content.Model.KeyStatisticText", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.ContentBlock", b => + { + b.Navigation("Comments"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.ContentSection", b => + { + b.Navigation("Content"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.DataImport", b => + { + b.Navigation("Errors"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.File", b => + { + b.Navigation("DataSetFileVersionGeographicLevels"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.Methodology", b => + { + b.Navigation("Publications"); + + b.Navigation("Versions"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.MethodologyVersion", b => + { + b.Navigation("MethodologyContent") + .IsRequired(); + + b.Navigation("MethodologyRedirects"); + + b.Navigation("Notes"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.Publication", b => + { + b.Navigation("Methodologies"); + + b.Navigation("PublicationRedirects"); + + b.Navigation("ReleaseVersions"); + + b.Navigation("Releases"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.Release", b => + { + b.Navigation("ReleaseRedirects"); + + b.Navigation("Versions"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.ReleaseVersion", b => + { + b.Navigation("Content"); + + b.Navigation("DataBlockVersions"); + + b.Navigation("FeaturedTables"); + + b.Navigation("KeyStatistics"); + + b.Navigation("ReleaseStatuses"); + + b.Navigation("Updates"); + }); + + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.Theme", b => + { + b.Navigation("Publications"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/GovUk.Education.ExploreEducationStatistics.Admin/Migrations/ContentMigrations/20241219093820_EES5738_CreateDataSetFileGeographicLevelsTable.cs b/src/GovUk.Education.ExploreEducationStatistics.Admin/Migrations/ContentMigrations/20241219093820_EES5738_CreateDataSetFileGeographicLevelsTable.cs new file mode 100644 index 00000000000..7a652fbef83 --- /dev/null +++ b/src/GovUk.Education.ExploreEducationStatistics.Admin/Migrations/ContentMigrations/20241219093820_EES5738_CreateDataSetFileGeographicLevelsTable.cs @@ -0,0 +1,51 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace GovUk.Education.ExploreEducationStatistics.Admin.Migrations.ContentMigrations +{ + /// + public partial class EES5738_CreateDataSetFileGeographicLevelsTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "DataSetFileVersionGeographicLevels", + columns: table => new + { + DataSetFileVersionId = table.Column(type: "uniqueidentifier", nullable: false), + GeographicLevel = table.Column(type: "nvarchar(6)", maxLength: 6, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DataSetFileVersionGeographicLevels", x => new { x.DataSetFileVersionId, x.GeographicLevel }); + table.ForeignKey( + name: "FK_DataSetFileVersionGeographicLevels_Files_DataSetFileVersionId", + column: x => x.DataSetFileVersionId, + principalTable: "Files", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.Sql("GRANT SELECT ON dbo.DataSetFileVersionGeographicLevels TO [content];"); + migrationBuilder.Sql("GRANT INSERT ON dbo.DataSetFileVersionGeographicLevels TO [importer];"); + + migrationBuilder.Sql("GRANT SELECT ON dbo.DataSetFileVersionGeographicLevels TO [data];"); + migrationBuilder.Sql("GRANT SELECT ON dbo.DataSetFileVersionGeographicLevels TO [publisher];"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("REVOKE SELECT ON dbo.DataSetFileGeographicLevels TO [content];"); + migrationBuilder.Sql("REVOKE INSERT ON dbo.DataSetFileGeographicLevels TO [importer];"); + + migrationBuilder.Sql("REVOKE SELECT ON dbo.DataSetFileGeographicLevels TO [data];"); + migrationBuilder.Sql("REVOKE SELECT ON dbo.DataSetFileGeographicLevels TO [publisher];"); + + migrationBuilder.DropTable( + name: "DataSetFileVersionGeographicLevels"); + } + } +} diff --git a/src/GovUk.Education.ExploreEducationStatistics.Admin/Migrations/ContentMigrations/ContentDbContextModelSnapshot.cs b/src/GovUk.Education.ExploreEducationStatistics.Admin/Migrations/ContentMigrations/ContentDbContextModelSnapshot.cs index 2877ed7a1a1..292daec9d7f 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Admin/Migrations/ContentMigrations/ContentDbContextModelSnapshot.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Admin/Migrations/ContentMigrations/ContentDbContextModelSnapshot.cs @@ -328,6 +328,20 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("DataImportErrors"); }); + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.DataSetFileVersionGeographicLevel", b => + { + b.Property("DataSetFileVersionId") + .HasColumnType("uniqueidentifier"); + + b.Property("GeographicLevel") + .HasMaxLength(6) + .HasColumnType("nvarchar(6)"); + + b.HasKey("DataSetFileVersionId", "GeographicLevel"); + + b.ToTable("DataSetFileVersionGeographicLevels"); + }); + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.EmbedBlock", b => { b.Property("Id") @@ -1594,6 +1608,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("DataImport"); }); + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.DataSetFileVersionGeographicLevel", b => + { + b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.File", "DataSetFileVersion") + .WithMany("DataSetFileVersionGeographicLevels") + .HasForeignKey("DataSetFileVersionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DataSetFileVersion"); + }); + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.FeaturedTable", b => { b.HasOne("GovUk.Education.ExploreEducationStatistics.Content.Model.User", "CreatedBy") @@ -2180,6 +2205,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Errors"); }); + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.File", b => + { + b.Navigation("DataSetFileVersionGeographicLevels"); + }); + modelBuilder.Entity("GovUk.Education.ExploreEducationStatistics.Content.Model.Methodology", b => { b.Navigation("Publications"); diff --git a/src/GovUk.Education.ExploreEducationStatistics.Common.Tests/FunctionsIntegrationTest.cs b/src/GovUk.Education.ExploreEducationStatistics.Common.Tests/FunctionsIntegrationTest.cs index 0a7cda58edf..9516e767bbc 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Common.Tests/FunctionsIntegrationTest.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Common.Tests/FunctionsIntegrationTest.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using System.Threading.Tasks; using Azure.Data.Tables; using GovUk.Education.ExploreEducationStatistics.Common.Extensions; diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Api.Tests/Controllers/DataSetFilesControllerCachingTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Api.Tests/Controllers/DataSetFilesControllerCachingTests.cs index 651ba30ac9d..efa82dcb7ee 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Api.Tests/Controllers/DataSetFilesControllerCachingTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Api.Tests/Controllers/DataSetFilesControllerCachingTests.cs @@ -31,6 +31,7 @@ public class ListDataSetsTests : DataSetFilesControllerCachingTests ThemeId: Guid.NewGuid(), PublicationId: Guid.NewGuid(), ReleaseId: Guid.NewGuid(), + GeographicLevel: GeographicLevel.Country.GetEnumValue(), LatestOnly: true, DataSetType: DataSetType.Api, SearchTerm: "term", @@ -116,6 +117,7 @@ public async Task NoCachedEntryExists_CreatesCache() _query.ThemeId, _query.PublicationId, _query.ReleaseId, + _query.GeographicLevelEnum, _query.LatestOnly, _query.DataSetType, _query.SearchTerm, diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Api.Tests/Controllers/DataSetFilesControllerTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Api.Tests/Controllers/DataSetFilesControllerTests.cs index 91a6b434769..b32dde93656 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Api.Tests/Controllers/DataSetFilesControllerTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Api.Tests/Controllers/DataSetFilesControllerTests.cs @@ -95,6 +95,45 @@ await TestApp.AddTestData(context => AssertResultsForExpectedReleaseFiles(publication1Release1Version1Files, pagedResult.Results); } + [Fact] + public async Task FilterByGeographicLevel_Success() + { + var (publication1, publication2) = _fixture + .DefaultPublication() + // Publications each have a published release version + .WithReleases([_fixture.DefaultRelease(publishedVersions: 1)]) + .WithTheme(_fixture.DefaultTheme()) + .GenerateTuple2(); + + ReleaseFile publication1Release1Version1File = _fixture.DefaultReleaseFile() + .WithReleaseVersion(publication1.ReleaseVersions[0]) + .WithFile(_fixture.DefaultFile(FileType.Data) + .WithDataSetFileVersionGeographicLevels( + [GeographicLevel.Country, GeographicLevel.LocalAuthority, GeographicLevel.Institution])); + + var publication2Release1Version1Files = GenerateDataSetFilesForReleaseVersion(publication2.ReleaseVersions[0]); + + await TestApp.AddTestData(context => + { + context.ReleaseFiles.Add(publication1Release1Version1File); + context.ReleaseFiles.AddRange(publication2Release1Version1Files); + }); + + MemoryCacheService + .SetupNotFoundForAnyKey>(); + + var query = new DataSetFileListRequest(GeographicLevel: GeographicLevel.Institution.GetEnumValue()); + var response = await ListDataSetFiles(query); + + MockUtils.VerifyAllMocks(MemoryCacheService); + + var pagedResult = response.AssertOk>(); + + pagedResult.AssertHasExpectedPagingAndResultCount( + expectedTotalResults: 1); + AssertResultsForExpectedReleaseFiles([publication1Release1Version1File], pagedResult.Results); + } + [Fact] public async Task FilterByPublicationId_Success() { @@ -1675,6 +1714,7 @@ private async Task ListDataSetFiles( { "themeId", request.ThemeId?.ToString() }, { "publicationId", request.PublicationId?.ToString() }, { "releaseId", request.ReleaseId?.ToString() }, + { "geographicLevel", request.GeographicLevel }, { "latestOnly", request.LatestOnly?.ToString() }, { "searchTerm", request.SearchTerm }, { "sort", request.Sort?.ToString() }, @@ -1737,6 +1777,7 @@ private List GenerateDataSetFilesForReleaseVersion( .WithReleaseVersion(releaseVersion) .WithFiles(_fixture.DefaultFile(FileType.Data) .WithDataSetFileMeta(_fixture.DefaultDataSetFileMeta()) + .WithDataSetFileVersionGeographicLevels([GeographicLevel.Country]) .GenerateList(numberOfDataSets)) .GenerateList(); } diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Api/Controllers/DataSetFilesController.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Api/Controllers/DataSetFilesController.cs index 015a79b28da..db48b8fc809 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Api/Controllers/DataSetFilesController.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Api/Controllers/DataSetFilesController.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using GovUk.Education.ExploreEducationStatistics.Common.Cache; using GovUk.Education.ExploreEducationStatistics.Common.Extensions; +using GovUk.Education.ExploreEducationStatistics.Common.Model.Data; using GovUk.Education.ExploreEducationStatistics.Common.ViewModels; using GovUk.Education.ExploreEducationStatistics.Content.Api.Cache; using GovUk.Education.ExploreEducationStatistics.Content.Requests; @@ -39,6 +40,7 @@ public async Task WithSubjectId( public static Generator WithDefaultFiles( this Generator generator, - string dataFileName) - => generator.ForInstance(s => s.SetDefaultFiles(dataFileName)); + string dataFileName, + bool metaSet = true) + { + if (metaSet) + { + return generator.ForInstance(s => s.SetDefaultFiles(dataFileName)); + } + return generator.ForInstance(s => s.SetDefaultFilesWithoutMeta(dataFileName)); + } public static Generator WithFile( this Generator generator, @@ -98,6 +105,29 @@ public static InstanceSetters SetDefaultFiles( ) .Set(d => d.MetaFileId, (_, d) => d.MetaFile.Id); + public static InstanceSetters SetDefaultFilesWithoutMeta( + this InstanceSetters setters, + string dataFileName) + => setters + .Set( + d => d.File, + (_, d, context) => context.Fixture + .DefaultFile(FileType.Data) + .WithDataSetFileMeta(null) + .WithDataSetFileVersionGeographicLevels([]) + .WithFilename($"{dataFileName}.csv") + .WithSubjectId(d.SubjectId) + ) + .Set(d => d.FileId, (_, d) => d.File.Id) + .Set( + d => d.MetaFile, + (_, d, context) => context.Fixture + .DefaultFile(FileType.Metadata) + .WithFilename($"{dataFileName}.meta.csv") + .WithSubjectId(d.SubjectId) + ) + .Set(d => d.MetaFileId, (_, d) => d.MetaFile.Id); + public static InstanceSetters SetFile( this InstanceSetters setters, File file) diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Model.Tests/Fixtures/DataSetFileVersionGeographicLevelGeneratorExtensions.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Model.Tests/Fixtures/DataSetFileVersionGeographicLevelGeneratorExtensions.cs new file mode 100644 index 00000000000..eb49d2897ec --- /dev/null +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Model.Tests/Fixtures/DataSetFileVersionGeographicLevelGeneratorExtensions.cs @@ -0,0 +1,51 @@ +using System; +using GovUk.Education.ExploreEducationStatistics.Common.Model.Data; +using GovUk.Education.ExploreEducationStatistics.Common.Tests.Fixtures; + +namespace GovUk.Education.ExploreEducationStatistics.Content.Model.Tests.Fixtures; + +public static class DataSetFileVersionGeographicLevelGeneratorExtensions +{ + public static Generator DefaultDataSetFileVersionGeographicLevel(this DataFixture fixture) + => fixture.Generator().WithDefaults(); + + public static Generator WithDefaults(this Generator generator) + => generator.ForInstance(d => d.SetDefaults()); + + public static InstanceSetters SetDefaults( + this InstanceSetters setters) + => setters + .SetDefault(p => p.DataSetFileVersionId); + + public static Generator WithDataSetFileVersion( + this Generator generator, + File dataSetFileVersion) + => generator.ForInstance(s => s.SetDataSetFileVersion(dataSetFileVersion)); + + public static Generator WithDataSetFileVersionId( + this Generator generator, + Guid dataSetFileVersionId) + => generator.ForInstance(s => s.SetDataSetFileVersionId(dataSetFileVersionId)); + + public static Generator WithGeographicLevel( + this Generator generator, + GeographicLevel geographicLevel) + => generator.ForInstance(s => s.SetGeographicLevel(geographicLevel)); + + public static InstanceSetters SetDataSetFileVersion( + this InstanceSetters setters, + File dataSetFileVersion) + => setters + .Set(f => f.DataSetFileVersion, dataSetFileVersion) + .Set(f => f.DataSetFileVersionId, dataSetFileVersion.Id); + + public static InstanceSetters SetDataSetFileVersionId( + this InstanceSetters setters, + Guid dataSetFileVersionId) + => setters.Set(f => f.DataSetFileVersionId, dataSetFileVersionId); + + public static InstanceSetters SetGeographicLevel( + this InstanceSetters setters, + GeographicLevel geographicLevel) + => setters.Set(f => f.GeographicLevel, geographicLevel); +} diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Model.Tests/Fixtures/FileGeneratorExtensions.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Model.Tests/Fixtures/FileGeneratorExtensions.cs index f45b0e99adc..320c2950442 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Model.Tests/Fixtures/FileGeneratorExtensions.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Model.Tests/Fixtures/FileGeneratorExtensions.cs @@ -1,8 +1,9 @@ using System; +using System.Collections.Generic; +using System.Linq; using GovUk.Education.ExploreEducationStatistics.Common.Model; using GovUk.Education.ExploreEducationStatistics.Common.Model.Data; using GovUk.Education.ExploreEducationStatistics.Common.Tests.Fixtures; -using Semver; namespace GovUk.Education.ExploreEducationStatistics.Content.Model.Tests.Fixtures; @@ -84,6 +85,11 @@ public static Generator WithDataSetFileMeta( DataSetFileMeta? dataSetFileMeta) => generator.ForInstance(s => s.SetDataSetFileMeta(dataSetFileMeta)); + public static Generator WithDataSetFileVersionGeographicLevels( + this Generator generator, + List geographicLevels) + => generator.ForInstance(s => s.SetDataSetFileVersionGeographicLevels(geographicLevels)); + public static InstanceSetters SetDefaults(this InstanceSetters setters, FileType? fileType) => fileType switch { @@ -110,7 +116,8 @@ public static InstanceSetters SetDataFileDefaults(this InstanceSetters f.SubjectId) .SetContentType("text/csv") .SetDefault(f => f.DataSetFileId) - .Set(f => f.DataSetFileMeta, (_, _, context) => context.Fixture.DefaultDataSetFileMeta()); + .Set(f => f.DataSetFileMeta, (_, _, context) => context.Fixture.DefaultDataSetFileMeta()) + .SetDataSetFileVersionGeographicLevels([GeographicLevel.Country, GeographicLevel.LocalAuthority, GeographicLevel.LocalAuthorityDistrict]); public static InstanceSetters SetMetaFileDefaults(this InstanceSetters setters) => setters @@ -200,4 +207,17 @@ public static InstanceSetters SetDataSetFileMeta( this InstanceSetters setters, DataSetFileMeta? dataSetFileMeta) => setters.Set(f => f.DataSetFileMeta, dataSetFileMeta); + + public static InstanceSetters SetDataSetFileVersionGeographicLevels( + this InstanceSetters setters, + List geographicLevels) + => setters.Set( + file => file.DataSetFileVersionGeographicLevels, + (_, file) => geographicLevels.Select( + gl => new DataSetFileVersionGeographicLevel + { + DataSetFileVersionId = file.Id, + DataSetFileVersion = file, + GeographicLevel = gl, + }).ToList()); } diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/DataSetFileVersionGeographicLevel.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/DataSetFileVersionGeographicLevel.cs new file mode 100644 index 00000000000..d677283dbe6 --- /dev/null +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/DataSetFileVersionGeographicLevel.cs @@ -0,0 +1,14 @@ +#nullable enable +using System; +using GovUk.Education.ExploreEducationStatistics.Common.Model.Data; + +namespace GovUk.Education.ExploreEducationStatistics.Content.Model; + +public class DataSetFileVersionGeographicLevel +{ + public Guid DataSetFileVersionId { get; set; } // Currently Files.Id, but will become DataSetFileVersion.Id in EES-5105 + + public File DataSetFileVersion { get; set; } = null!; + + public GeographicLevel GeographicLevel { get; set; } +} diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Database/ContentDbContext.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Database/ContentDbContext.cs index a86f10300d4..8521a3ede07 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Database/ContentDbContext.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/Database/ContentDbContext.cs @@ -55,6 +55,7 @@ private void Configure(bool updateTimestamps = true) public virtual DbSet ReleaseStatus { get; set; } public virtual DbSet ReleaseFiles { get; set; } public virtual DbSet Files { get; set; } + public virtual DbSet DataSetFileVersionGeographicLevels { get; set; } public virtual DbSet ContentSections { get; set; } public virtual DbSet ContentBlocks { get; set; } public virtual DbSet KeyStatistics { get; set; } @@ -110,6 +111,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) ConfigureReleaseStatus(modelBuilder); ConfigureReleaseFile(modelBuilder); ConfigureFile(modelBuilder); + ConfigureDataSetFileVersionGeographicLevel(modelBuilder); ConfigureContentBlock(modelBuilder); ConfigureContentSection(modelBuilder); ConfigureReleaseVersion(modelBuilder); @@ -453,12 +455,28 @@ private static void ConfigureFile(ModelBuilder modelBuilder) ? DateTime.SpecifyKind(v.Value, DateTimeKind.Utc) : null); entity.Property(p => p.DataSetFileMeta) - .HasConversion( // You might want to use EF8 JSON support instead of this + .HasConversion( v => JsonConvert.SerializeObject(v), v => JsonConvert.DeserializeObject(v)); }); } + private static void ConfigureDataSetFileVersionGeographicLevel(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.HasKey(gl => new + { + gl.DataSetFileVersionId, + gl.GeographicLevel + }); + + entity.Property(gl => gl.GeographicLevel) + .HasMaxLength(6) + .HasConversion(new EnumToEnumValueConverter()); + }); + } + private static void ConfigureContentBlock(ModelBuilder modelBuilder) { modelBuilder.Entity(entity => diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/File.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/File.cs index f9a9ac9fd22..7ada31a8934 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Model/File.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Model/File.cs @@ -1,5 +1,6 @@ #nullable enable using System; +using System.Collections.Generic; using GovUk.Education.ExploreEducationStatistics.Common.Model; namespace GovUk.Education.ExploreEducationStatistics.Content.Model @@ -22,6 +23,8 @@ public class File : ICreatedTimestamp public int? DataSetFileVersion { get; set; } + public List DataSetFileVersionGeographicLevels { get; set; } = []; + public DataSetFileMeta? DataSetFileMeta { get; set; } public Guid? ReplacedById { get; set; } diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Requests/DataSetFileListRequest.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Requests/DataSetFileListRequest.cs index 7fe5f5be0e1..c4f1bb90e8c 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Requests/DataSetFileListRequest.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Requests/DataSetFileListRequest.cs @@ -1,5 +1,8 @@ using FluentValidation; using GovUk.Education.ExploreEducationStatistics.Common.Model; +using GovUk.Education.ExploreEducationStatistics.Common.Model.Data; +using GovUk.Education.ExploreEducationStatistics.Common.Utils; +using GovUk.Education.ExploreEducationStatistics.Common.Validators; namespace GovUk.Education.ExploreEducationStatistics.Content.Requests; @@ -7,6 +10,7 @@ public record DataSetFileListRequest( Guid? ThemeId = null, Guid? PublicationId = null, Guid? ReleaseId = null, + string? GeographicLevel = null, bool? LatestOnly = null, DataSetType? DataSetType = null, string? SearchTerm = null, @@ -15,6 +19,11 @@ public record DataSetFileListRequest( int Page = 1, int PageSize = 10) { + public GeographicLevel? GeographicLevelEnum => + GeographicLevel == null + ? null + : EnumUtil.GetFromEnumValue(GeographicLevel); + public class Validator : AbstractValidator { public Validator() @@ -23,6 +32,9 @@ public Validator() .MinimumLength(3); RuleFor(request => request.ReleaseId).NotEmpty() .When(request => request.Sort == DataSetsListRequestSortBy.Natural); + RuleFor(request => request.GeographicLevel) + .AllowedValue(EnumUtil.GetEnumValues()) + .When(request => request.GeographicLevel != null); RuleFor(request => request.SearchTerm).NotEmpty() .When(request => request.Sort == DataSetsListRequestSortBy.Relevance); RuleFor(request => request.Page) diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Services/DataSetFileService.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Services/DataSetFileService.cs index ab71aad330e..e6d0e9d85d9 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Services/DataSetFileService.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Services/DataSetFileService.cs @@ -26,35 +26,25 @@ using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; +using GovUk.Education.ExploreEducationStatistics.Common.Model.Data; using static GovUk.Education.ExploreEducationStatistics.Common.Model.SortDirection; using static GovUk.Education.ExploreEducationStatistics.Content.Requests.DataSetsListRequestSortBy; using ReleaseVersion = GovUk.Education.ExploreEducationStatistics.Content.Model.ReleaseVersion; namespace GovUk.Education.ExploreEducationStatistics.Content.Services; -public class DataSetFileService : IDataSetFileService +public class DataSetFileService( + ContentDbContext contentDbContext, + IReleaseVersionRepository releaseVersionRepository, + IPublicBlobStorageService publicBlobStorageService, + IFootnoteRepository footnoteRepository) + : IDataSetFileService { - private readonly ContentDbContext _contentDbContext; - private readonly IReleaseVersionRepository _releaseVersionRepository; - private readonly IPublicBlobStorageService _publicBlobStorageService; - private readonly IFootnoteRepository _footnoteRepository; - - public DataSetFileService( - ContentDbContext contentDbContext, - IReleaseVersionRepository releaseVersionRepository, - IPublicBlobStorageService publicBlobStorageService, - IFootnoteRepository footnoteRepository) - { - _contentDbContext = contentDbContext; - _releaseVersionRepository = releaseVersionRepository; - _publicBlobStorageService = publicBlobStorageService; - _footnoteRepository = footnoteRepository; - } - public async Task>> ListDataSetFiles( Guid? themeId, Guid? publicationId, Guid? releaseVersionId, + GeographicLevel? geographicLevel, bool? latestOnly, DataSetType? dataSetType, string? searchTerm, @@ -73,18 +63,19 @@ public async Task rf.Id, searchTerm); + .JoinFreeText(contentDbContext.ReleaseFilesFreeTextTable, rf => rf.Id, searchTerm); var results = await query .OrderBy(sort.Value, sortDirection.Value) @@ -144,10 +135,10 @@ private static Expression, DataSetFileSumm public async Task>> ListSitemapItems( CancellationToken cancellationToken = default) { - var latestReleaseVersions = _contentDbContext.ReleaseVersions + var latestReleaseVersions = contentDbContext.ReleaseVersions .LatestReleaseVersions(publishedOnly: true); - var latestReleaseFiles = _contentDbContext.ReleaseFiles + var latestReleaseFiles = contentDbContext.ReleaseFiles .AsNoTracking() .OfFileType(FileType.Data) .HavingNoDataReplacementInProgress() @@ -176,7 +167,7 @@ private static async Task> ChangeSummaryHtmlTo public async Task> GetDataSetFile(Guid dataSetFileId) { - var releaseFile = await _contentDbContext.ReleaseFiles + var releaseFile = await contentDbContext.ReleaseFiles .Include(rf => rf.ReleaseVersion.Publication.Theme) .Include(rf => rf.ReleaseVersion.Publication.SupersededBy) .Include(rf => rf.File) @@ -188,7 +179,7 @@ public async Task> GetDataSetFile(Gui .FirstOrDefaultAsync(); if (releaseFile == null - || !await _releaseVersionRepository.IsLatestPublishedReleaseVersion( + || !await releaseVersionRepository.IsLatestPublishedReleaseVersion( releaseFile.ReleaseVersionId)) { return new NotFoundResult(); @@ -198,7 +189,7 @@ public async Task> GetDataSetFile(Gui var variables = GetVariables(releaseFile.File.DataSetFileMeta!); - var footnotes = await _footnoteRepository.GetFootnotes( + var footnotes = await footnoteRepository.GetFootnotes( releaseFile.ReleaseVersionId, releaseFile.File.SubjectId); @@ -249,7 +240,7 @@ public async Task> GetDataSetFile(Gui public async Task DownloadDataSetFile( Guid dataSetFileId) { - var releaseFile = await _contentDbContext.ReleaseFiles + var releaseFile = await contentDbContext.ReleaseFiles .Include(rf => rf.File) .Where(rf => rf.File.DataSetFileId == dataSetFileId @@ -259,13 +250,13 @@ public async Task DownloadDataSetFile( .FirstOrDefaultAsync(); if (releaseFile == null - || !await _releaseVersionRepository.IsLatestPublishedReleaseVersion( + || !await releaseVersionRepository.IsLatestPublishedReleaseVersion( releaseFile.ReleaseVersionId)) { return new NotFoundResult(); } - var stream = await _publicBlobStorageService.StreamBlob( + var stream = await publicBlobStorageService.StreamBlob( containerName: BlobContainers.PublicReleaseFiles, path: releaseFile.PublicPath()); @@ -306,7 +297,7 @@ private static DataSetFileMetaViewModel BuildDataSetFileMetaViewModel( private async Task GetDataCsvPreview(ReleaseFile releaseFile) { - var datafileStreamProvider = () => _publicBlobStorageService.StreamBlob( + var datafileStreamProvider = () => publicBlobStorageService.StreamBlob( containerName: BlobContainers.PublicReleaseFiles, path: releaseFile.PublicPath()); @@ -491,6 +482,16 @@ internal static IQueryable HavingReleaseVersionId( return releaseVersionId.HasValue ? query.Where(rf => rf.ReleaseVersionId == releaseVersionId.Value) : query; } + internal static IQueryable HavingGeographicLevel( + this IQueryable query, + GeographicLevel? geographicLevel) + { + return geographicLevel.HasValue + ? query.Where(rf => rf.File.DataSetFileVersionGeographicLevels.Any( + gl => gl.GeographicLevel == geographicLevel)) + : query; + } + internal static IQueryable OfDataSetType( this IQueryable query, DataSetType dataSetType) diff --git a/src/GovUk.Education.ExploreEducationStatistics.Content.Services/Interfaces/IDataSetFileService.cs b/src/GovUk.Education.ExploreEducationStatistics.Content.Services/Interfaces/IDataSetFileService.cs index 1b5cb2a3372..0a16d4cb669 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Content.Services/Interfaces/IDataSetFileService.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Content.Services/Interfaces/IDataSetFileService.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using GovUk.Education.ExploreEducationStatistics.Common.Model; +using GovUk.Education.ExploreEducationStatistics.Common.Model.Data; using GovUk.Education.ExploreEducationStatistics.Common.ViewModels; using GovUk.Education.ExploreEducationStatistics.Content.Requests; using GovUk.Education.ExploreEducationStatistics.Content.ViewModels; @@ -17,6 +18,7 @@ Task>> Guid? themeId, Guid? publicationId, Guid? releaseVersionId, + GeographicLevel? geographicLevel, bool? latestOnly, DataSetType? dataSetType, string? searchTerm, diff --git a/src/GovUk.Education.ExploreEducationStatistics.Data.Processor.Tests/Functions/ProcessorStage3Tests.cs b/src/GovUk.Education.ExploreEducationStatistics.Data.Processor.Tests/Functions/ProcessorStage3Tests.cs index 36f3fdc9e87..965c0c781e1 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Data.Processor.Tests/Functions/ProcessorStage3Tests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Data.Processor.Tests/Functions/ProcessorStage3Tests.cs @@ -23,6 +23,7 @@ using GovUk.Education.ExploreEducationStatistics.Data.Processor.Services; using GovUk.Education.ExploreEducationStatistics.Data.Processor.Services.Interfaces; using GovUk.Education.ExploreEducationStatistics.Data.Processor.Tests.Services; +using GovUk.Education.ExploreEducationStatistics.Public.Data.Model.Migrations; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -106,7 +107,7 @@ public async Task ProcessStage3() var import = _fixture .DefaultDataImport() .WithSubjectId(_subject.Id) - .WithDefaultFiles("small-csv") + .WithDefaultFiles("small-csv", metaSet: false) .WithStatus(STAGE_3) .WithTotalRows(16) .WithExpectedImportedRows(16) @@ -325,7 +326,7 @@ public async Task ProcessStage3_FailsImportIfRowCountsDontMatch() var import = _fixture .DefaultDataImport() .WithSubjectId(_subject.Id) - .WithDefaultFiles("small-csv") + .WithDefaultFiles("small-csv", metaSet: false) .WithStatus(STAGE_3) .WithTotalRows(16) .WithExpectedImportedRows(16) @@ -454,7 +455,7 @@ public async Task ProcessStage3_PartiallyImportedAlready() var import = _fixture .DefaultDataImport() .WithSubjectId(_subject.Id) - .WithDefaultFiles("small-csv") + .WithDefaultFiles("small-csv", metaSet: false) .WithStatus(STAGE_3) .WithTotalRows(16) .WithExpectedImportedRows(16) @@ -604,7 +605,7 @@ public async Task ProcessStage3_PartiallyImportedAlready_BatchSizeChanged() var import = _fixture .DefaultDataImport() .WithSubjectId(_subject.Id) - .WithDefaultFiles("small-csv") + .WithDefaultFiles("small-csv", metaSet: false) .WithStatus(STAGE_3) .WithTotalRows(16) .WithExpectedImportedRows(16) @@ -749,7 +750,7 @@ public async Task ProcessStage3_IgnoredRows() var import = _fixture .DefaultDataImport() .WithSubjectId(_subject.Id) - .WithDefaultFiles("ignored-school-rows") + .WithDefaultFiles("ignored-school-rows", metaSet: false) .WithStatus(STAGE_3) .WithTotalRows(16) .WithExpectedImportedRows(8) @@ -894,7 +895,7 @@ public async Task ProcessStage3_IgnoredRows_PartiallyImported() var import = _fixture .DefaultDataImport() .WithSubjectId(_subject.Id) - .WithDefaultFiles("ignored-school-rows") + .WithDefaultFiles("ignored-school-rows", metaSet: false) .WithStatus(STAGE_3) .WithTotalRows(16) .WithExpectedImportedRows(8) @@ -1052,7 +1053,7 @@ public async Task ProcessStage3_Cancelling() var import = _fixture .DefaultDataImport() .WithSubjectId(_subject.Id) - .WithDefaultFiles("small-csv") + .WithDefaultFiles("small-csv", metaSet: false) .WithStatus(STAGE_3) .WithTotalRows(16) .WithExpectedImportedRows(16) @@ -1190,7 +1191,7 @@ public async Task ProcessStage3_CancelledAlready() var import = _fixture .DefaultDataImport() .WithSubjectId(_subject.Id) - .WithDefaultFiles("small-csv") + .WithDefaultFiles("small-csv", metaSet: false) .WithStatus(CANCELLED) .WithTotalRows(16) .WithExpectedImportedRows(16) @@ -1324,7 +1325,7 @@ public async Task ProcessStage3_AdditionalFiltersAndIndicators() var import = _fixture .DefaultDataImport() .WithSubjectId(_subject.Id) - .WithDefaultFiles("additional-filters-and-indicators") + .WithDefaultFiles("additional-filters-and-indicators", metaSet: false) .WithStatus(STAGE_3) .WithTotalRows(16) .WithExpectedImportedRows(16) @@ -1493,7 +1494,7 @@ public async Task ProcessStage3_SpecialFilterItemAndIndicatorValues() var import = _fixture .DefaultDataImport() .WithSubjectId(subject.Id) - .WithDefaultFiles("small-csv-with-special-data") + .WithDefaultFiles("small-csv-with-special-data", metaSet: false) .WithStatus(STAGE_3) .WithTotalRows(5) .WithExpectedImportedRows(5) diff --git a/src/GovUk.Education.ExploreEducationStatistics.Data.Processor.Tests/Services/DataImportServiceTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Data.Processor.Tests/Services/DataImportServiceTests.cs index f0aabc50536..de19c60ad62 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Data.Processor.Tests/Services/DataImportServiceTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Data.Processor.Tests/Services/DataImportServiceTests.cs @@ -220,6 +220,7 @@ public async Task WriteDataSetMetaFile_Success() var file = _fixture.DefaultFile(FileType.Data) .WithDataSetFileMeta(null) + .WithDataSetFileVersionGeographicLevels([]) .WithSubjectId(subject.Id) .Generate(); @@ -284,7 +285,7 @@ public async Task WriteDataSetMetaFile_Success() contentDbContextId, statisticsDbContextId); - await service.WriteDataSetFileMeta(subject.Id); + await service.WriteDataSetFileMeta(file.Id, subject.Id); await using (var contentDbContext = InMemoryContentDbContext(contentDbContextId)) { diff --git a/src/GovUk.Education.ExploreEducationStatistics.Data.Processor.Tests/Services/FileImportServiceTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Data.Processor.Tests/Services/FileImportServiceTests.cs index c33c514b8a8..eaaa1f0c9f7 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Data.Processor.Tests/Services/FileImportServiceTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Data.Processor.Tests/Services/FileImportServiceTests.cs @@ -60,6 +60,7 @@ public async Task CompleteImport() dataImportService .Setup(s => s.WriteDataSetFileMeta( + import.FileId, import.SubjectId)) .Returns(Task.CompletedTask); @@ -228,7 +229,9 @@ await FinishedStatuses var dataImportService = new Mock(Strict); if (finishedStatus == COMPLETE) { - dataImportService.Setup(mock => mock.WriteDataSetFileMeta(import.SubjectId)) + dataImportService.Setup(mock => mock.WriteDataSetFileMeta( + import.FileId, + import.SubjectId)) .Returns(Task.CompletedTask); } diff --git a/src/GovUk.Education.ExploreEducationStatistics.Data.Processor/Services/DataImportService.cs b/src/GovUk.Education.ExploreEducationStatistics.Data.Processor/Services/DataImportService.cs index 301d89cd76a..5b29fd83184 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Data.Processor/Services/DataImportService.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Data.Processor/Services/DataImportService.cs @@ -151,7 +151,7 @@ public async Task UpdateStatus(Guid id, DataImportStatus newStatus, double perce await context.SaveChangesAsync(); } - public async Task WriteDataSetFileMeta(Guid subjectId) + public async Task WriteDataSetFileMeta(Guid fileId, Guid subjectId) { await using var contentDbContext = _dbContextSupplier.CreateDbContext(); await using var statisticsDbContext = _dbContextSupplier.CreateDbContext(); @@ -220,6 +220,16 @@ public async Task WriteDataSetFileMeta(Guid subjectId) .Single(f => f.Type == FileType.Data && f.SubjectId == subjectId); file.DataSetFileMeta = dataSetFileMeta; + + var dataSetFileVersionGeographicLevels = geographicLevels + .Select(gl => new DataSetFileVersionGeographicLevel + { + DataSetFileVersionId = fileId, + GeographicLevel = gl, + }).ToList(); + contentDbContext.DataSetFileVersionGeographicLevels.AddRange( + dataSetFileVersionGeographicLevels); + await contentDbContext.SaveChangesAsync(); } } diff --git a/src/GovUk.Education.ExploreEducationStatistics.Data.Processor/Services/FileImportService.cs b/src/GovUk.Education.ExploreEducationStatistics.Data.Processor/Services/FileImportService.cs index 44daca0a939..c2c9550cdd0 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Data.Processor/Services/FileImportService.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Data.Processor/Services/FileImportService.cs @@ -105,7 +105,7 @@ public async Task CompleteImport(DataImport import, StatisticsDbContext context) if (import.Status == COMPLETE) { - await _dataImportService.WriteDataSetFileMeta(import.SubjectId); + await _dataImportService.WriteDataSetFileMeta(import.FileId, import.SubjectId); } return; @@ -139,7 +139,7 @@ await _dataImportService.FailImport(import.Id, if (import.Errors.Count == 0) { await _dataImportService.UpdateStatus(import.Id, COMPLETE, 100); - await _dataImportService.WriteDataSetFileMeta(import.SubjectId); + await _dataImportService.WriteDataSetFileMeta(import.FileId, import.SubjectId); } else { diff --git a/src/GovUk.Education.ExploreEducationStatistics.Data.Processor/Services/Interfaces/IDataImportService.cs b/src/GovUk.Education.ExploreEducationStatistics.Data.Processor/Services/Interfaces/IDataImportService.cs index e6ea8ba07ba..1ece36d23a5 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Data.Processor/Services/Interfaces/IDataImportService.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Data.Processor/Services/Interfaces/IDataImportService.cs @@ -19,7 +19,7 @@ public interface IDataImportService Task UpdateStatus(Guid id, DataImportStatus newStatus, double percentageComplete); - Task WriteDataSetFileMeta(Guid subjectId); + Task WriteDataSetFileMeta(Guid fileId, Guid subjectId); Task Update( Guid id, diff --git a/src/explore-education-statistics-common/src/utils/locationLevelsMap.ts b/src/explore-education-statistics-common/src/utils/locationLevelsMap.ts index 6f54b04f835..947dd7b3049 100644 --- a/src/explore-education-statistics-common/src/utils/locationLevelsMap.ts +++ b/src/explore-education-statistics-common/src/utils/locationLevelsMap.ts @@ -22,6 +22,7 @@ export type GeographicLevelCode = | 'WARD'; export interface LocationLevelDetails { + filterLabel: string; // used in the dropdown that filters data sets by geographic level label: string; plural: string; prefix: string; @@ -30,108 +31,126 @@ export interface LocationLevelDetails { const locationLevelsMap = { country: { + filterLabel: 'National', label: 'Country', plural: 'Countries', prefix: 'a', code: 'NAT', }, englishDevolvedArea: { + filterLabel: 'English Devolved Area', label: 'English Devolved Area', plural: 'English Devolved Areas', prefix: 'an', code: 'EDA', }, institution: { + filterLabel: 'Institution', label: 'Institution', plural: 'Institutions', prefix: 'an', code: 'INST', }, localAuthority: { + filterLabel: 'Local Authority', label: 'Local Authority', plural: 'Local Authorities', prefix: 'a', code: 'LA', }, localAuthorityDistrict: { + filterLabel: 'Local Authority District', label: 'Local Authority District', plural: 'Local Authority Districts', prefix: 'a', code: 'LAD', }, localEnterprisePartnership: { + filterLabel: 'Local Enterprise Partnership', label: 'Local Enterprise Partnership', plural: 'Local Enterprise Partnerships', prefix: 'a', code: 'LEP', }, localSkillsImprovementPlanArea: { + filterLabel: 'Local Skills Improvement Plan Area', label: 'Local Skills Improvement Plan Area', plural: 'Local Skills Improvement Plan Areas', prefix: 'a', code: 'LSIP', }, mayoralCombinedAuthority: { + filterLabel: 'Mayoral Combined Authority', label: 'Mayoral Combined Authority', plural: 'Mayoral Combined Authorities', prefix: 'a', code: 'MCA', }, multiAcademyTrust: { + filterLabel: 'Multi Academy Trust', label: 'Multi Academy Trust', plural: 'Multi Academy Trusts', prefix: 'a', code: 'MAT', }, opportunityArea: { + filterLabel: 'Opportunity Area', label: 'Opportunity Area', plural: 'Opportunity Areas', prefix: 'an', code: 'OA', }, parliamentaryConstituency: { + filterLabel: 'Parliamentary Constituency', label: 'Parliamentary Constituency', plural: 'Parliamentary Constituencies', prefix: 'a', code: 'PCON', }, planningArea: { + filterLabel: 'Planning Area', label: 'Planning Area', plural: 'Planning Areas', prefix: 'a', code: 'PA', }, provider: { + filterLabel: 'Provider', label: 'Provider', plural: 'Providers', prefix: 'a', code: 'PROV', }, region: { + filterLabel: 'Region', label: 'Region', plural: 'Regions', prefix: 'a', code: 'REG', }, rscRegion: { + filterLabel: 'RSC Region', label: 'RSC Region', plural: 'RSC Regions', prefix: 'an', code: 'RSC', }, school: { + filterLabel: 'School', label: 'School', plural: 'Schools', prefix: 'a', code: 'SCH', }, sponsor: { + filterLabel: 'Sponsor', label: 'Sponsor', plural: 'Sponsors', prefix: 'a', code: 'SPON', }, ward: { + filterLabel: 'Ward', label: 'Ward', plural: 'Wards', prefix: 'a', diff --git a/src/explore-education-statistics-frontend/src/modules/data-catalogue/DataCataloguePage.tsx b/src/explore-education-statistics-frontend/src/modules/data-catalogue/DataCataloguePage.tsx index e551420f4d2..039b1dd1c12 100644 --- a/src/explore-education-statistics-frontend/src/modules/data-catalogue/DataCataloguePage.tsx +++ b/src/explore-education-statistics-frontend/src/modules/data-catalogue/DataCataloguePage.tsx @@ -52,6 +52,11 @@ import omit from 'lodash/omit'; import Head from 'next/head'; import { useRouter } from 'next/router'; import { ParsedUrlQuery } from 'querystring'; +import locationLevelsMap, { + GeographicLevelCode, + geographicLevelCodesMap, + LocationLevelKey, +} from '@common/utils/locationLevelsMap'; const defaultPageTitle = 'Data catalogue'; @@ -61,6 +66,7 @@ export interface DataCataloguePageQuery { page?: number; publicationId?: string; releaseId?: string; + geographicLevel?: GeographicLevelCode; searchTerm?: string; sortBy?: DataSetFileSortOption; sortDirection?: SortDirection; @@ -85,6 +91,7 @@ const DataCataloguePage: NextPage = ({ showTypeFilter }) => { sortBy, publicationId, releaseId, + geographicLevel, searchTerm, themeId, } = getParamsFromQuery(router.query); @@ -121,16 +128,22 @@ const DataCataloguePage: NextPage = ({ showTypeFilter }) => { const selectedRelease = releases.find(release => release.id === releaseId); + const selectedGeographicLevel = geographicLevel + ? geographicLevelCodesMap[geographicLevel] + : undefined; + const { paging, results: dataSets = [] } = dataSetsData ?? {}; const { page, totalPages, totalResults = 0 } = paging ?? {}; const [showAllDetails, toggleAllDetails] = useToggle(false); - const isFiltered = !!publicationId || !!searchTerm || !!themeId; + const isFiltered = + !!publicationId || !!searchTerm || !!themeId || !!geographicLevel; const filteredByString = compact([ searchTerm, selectedTheme?.title, selectedPublication?.title, + geographicLevel, ]).join(', '); const updateQueryParams = async (nextQuery: DataCataloguePageQuery) => { @@ -327,6 +340,7 @@ const DataCataloguePage: NextPage = ({ showTypeFilter }) => { publicationId={publicationId} publications={publications} releaseId={releaseId} + geographicLevel={geographicLevel} releases={releases} showResetFiltersButton={!isMobileMedia && isFiltered} showTypeFilter={showTypeFilter} @@ -415,6 +429,15 @@ const DataCataloguePage: NextPage = ({ showTypeFilter }) => { } /> )} + {selectedGeographicLevel && ( + + handleResetFilter({ filterType: 'geographicLevel' }) + } + /> + )} )} diff --git a/src/explore-education-statistics-frontend/src/modules/data-catalogue/__data__/testDataSets.ts b/src/explore-education-statistics-frontend/src/modules/data-catalogue/__data__/testDataSets.ts index c73c983240c..c6a2e4a13ca 100644 --- a/src/explore-education-statistics-frontend/src/modules/data-catalogue/__data__/testDataSets.ts +++ b/src/explore-education-statistics-frontend/src/modules/data-catalogue/__data__/testDataSets.ts @@ -77,7 +77,7 @@ export const testDataSetFileSummaries: DataSetFileSummary[] = [ to: '2020', }, filters: ['Filter 1', 'Filter 2'], - geographicLevels: ['National', 'Regional'], + geographicLevels: ['National', 'Regional', 'Local authority'], indicators: ['Indicator 1', 'Indicator 2'], }, latestData: true, diff --git a/src/explore-education-statistics-frontend/src/modules/data-catalogue/__tests__/DataCataloguePage.test.tsx b/src/explore-education-statistics-frontend/src/modules/data-catalogue/__tests__/DataCataloguePage.test.tsx index 4026e9df8c6..e6b684958f7 100644 --- a/src/explore-education-statistics-frontend/src/modules/data-catalogue/__tests__/DataCataloguePage.test.tsx +++ b/src/explore-education-statistics-frontend/src/modules/data-catalogue/__tests__/DataCataloguePage.test.tsx @@ -1195,6 +1195,32 @@ describe('DataCataloguePage', () => { ).toBeInTheDocument(); }); + test('filters by geographic level', async () => { + mockRouter.setCurrentUrl('/data-catalogue?geographicLevel=LA'); + + dataSetService.listDataSetFiles.mockResolvedValueOnce({ + results: [testDataSetFileSummaries[1], testDataSetFileSummaries[2]], + paging: { ...testPaging, totalPages: 1, totalResults: 1 }, + }); + publicationService.getPublicationTree.mockResolvedValue(testThemes); + publicationService.listReleases.mockResolvedValue(testReleases); + + render(); + + await waitFor(() => { + expect(screen.getByText('1 data set')).toBeInTheDocument(); + }); + + expect(mockRouter).toMatchObject({ + pathname: '/data-catalogue', + query: { geographicLevel: 'LA' }, + }); + + expect(screen.getByLabelText('Filter by Geographic level')).toHaveValue( + 'LA', + ); + }); + test('filters by search term', async () => { mockRouter.setCurrentUrl('/data-catalogue?searchTerm=find+me'); diff --git a/src/explore-education-statistics-frontend/src/modules/data-catalogue/components/Filters.tsx b/src/explore-education-statistics-frontend/src/modules/data-catalogue/components/Filters.tsx index 1dc33f5d1ed..a5e34d16eb0 100644 --- a/src/explore-education-statistics-frontend/src/modules/data-catalogue/components/Filters.tsx +++ b/src/explore-education-statistics-frontend/src/modules/data-catalogue/components/Filters.tsx @@ -13,6 +13,10 @@ import styles from '@frontend/modules/data-catalogue/components/Filters.module.s import { DataSetFileFilter } from '@frontend/modules/data-catalogue/utils/dataSetFileFilters'; import React from 'react'; import classNames from 'classnames'; +import locationLevelsMap, { + GeographicLevelCode, +} from '@common/utils/locationLevelsMap'; +import typedKeys from '@common/utils/object/typedKeys'; const formId = 'filters-form'; @@ -23,6 +27,7 @@ interface Props { publications?: PublicationTreeSummary[]; releaseId?: string; releases?: ReleaseSummary[]; + geographicLevel?: GeographicLevelCode; showResetFiltersButton?: boolean; showTypeFilter?: boolean; themeId?: string; @@ -44,6 +49,7 @@ export default function Filters({ publicationId, releaseId, releases = [], + geographicLevel, showResetFiltersButton, showTypeFilter, themeId, @@ -140,6 +146,36 @@ export default function Filters({ /> + + + Filter by Geographic level + + } + name="geographicLevel" + options={[ + { label: 'All', value: 'all' }, + ...typedKeys(locationLevelsMap).map(key => { + return { + label: locationLevelsMap[key].filterLabel, + value: locationLevelsMap[key].code, + }; + }), + ]} + value={geographicLevel ?? 'all'} + order={[]} + onChange={e => { + onChange({ + filterType: 'geographicLevel', + nextValue: e.target.value, + }); + }} + /> + + {showResetFiltersButton && ( { expect( within(screen.getByTestId('Geographic levels')).getByText( - 'National, Regional', + 'Local authority, National, Regional', ), ).toBeInTheDocument(); expect( @@ -83,7 +83,7 @@ describe('DataSetFileSummary', () => { expect( within(screen.getByTestId('Geographic levels')).getByText( - 'National, Regional', + 'Local authority, National, Regional', ), ).toBeInTheDocument(); expect( diff --git a/src/explore-education-statistics-frontend/src/modules/data-catalogue/utils/createDataSetFileListRequest.ts b/src/explore-education-statistics-frontend/src/modules/data-catalogue/utils/createDataSetFileListRequest.ts index b4aa5ec60eb..43e098a6766 100644 --- a/src/explore-education-statistics-frontend/src/modules/data-catalogue/utils/createDataSetFileListRequest.ts +++ b/src/explore-education-statistics-frontend/src/modules/data-catalogue/utils/createDataSetFileListRequest.ts @@ -23,6 +23,7 @@ export default function createDataSetFileListRequest( sortBy, publicationId, releaseId, + geographicLevel, searchTerm: searchParam, themeId, } = getParamsFromQuery(query); @@ -43,6 +44,7 @@ export default function createDataSetFileListRequest( page: parseNumber(query.page) ?? 1, publicationId, releaseId, + geographicLevel, sort, sortDirection, searchTerm, @@ -101,6 +103,7 @@ export function getParamsFromQuery(query: DataCataloguePageQuery) { : 'newest', publicationId: getFirst(query.publicationId), releaseId: getFirst(query.releaseId), + geographicLevel: getFirst(query.geographicLevel), searchTerm: getFirst(query.searchTerm), themeId: getFirst(query.themeId), }; diff --git a/src/explore-education-statistics-frontend/src/modules/data-catalogue/utils/dataSetFileFilters.ts b/src/explore-education-statistics-frontend/src/modules/data-catalogue/utils/dataSetFileFilters.ts index b5b1483af6e..4cb170433cb 100644 --- a/src/explore-education-statistics-frontend/src/modules/data-catalogue/utils/dataSetFileFilters.ts +++ b/src/explore-education-statistics-frontend/src/modules/data-catalogue/utils/dataSetFileFilters.ts @@ -3,6 +3,7 @@ export const dataSetFileFilters = [ 'latest', 'publicationId', 'releaseId', + 'geographicLevel', 'searchTerm', 'themeId', ] as const; diff --git a/src/explore-education-statistics-frontend/src/services/dataSetFileService.ts b/src/explore-education-statistics-frontend/src/services/dataSetFileService.ts index f8c367d2daa..99d85c8c5d2 100644 --- a/src/explore-education-statistics-frontend/src/services/dataSetFileService.ts +++ b/src/explore-education-statistics-frontend/src/services/dataSetFileService.ts @@ -2,6 +2,7 @@ import { PaginatedList } from '@common/services/types/pagination'; import { ReleaseType } from '@common/services/types/releaseType'; import { contentApi } from '@common/services/api'; import { SortDirection } from '@common/services/types/sort'; +import { GeographicLevelCode } from '@common/utils/locationLevelsMap'; export interface DataSetVariable { label: string; @@ -115,6 +116,7 @@ export interface DataSetFileListRequest { pageSize?: number; publicationId?: string; releaseId?: string; + geographicLevel?: GeographicLevelCode; searchTerm?: string; sort?: DataSetFileSortParam; sortDirection?: SortDirection; diff --git a/tests/robot-tests/tests/admin_and_public/bau/data_catalogue.robot b/tests/robot-tests/tests/admin_and_public/bau/data_catalogue.robot index d1781d21f42..659af1efdbc 100644 --- a/tests/robot-tests/tests/admin_and_public/bau/data_catalogue.robot +++ b/tests/robot-tests/tests/admin_and_public/bau/data_catalogue.robot @@ -212,6 +212,25 @@ Remove release filter user checks page does not contain button ${PUPIL_ABSENCE_RELEASE_NAME} user checks selected option label id:filters-form-release All releases +Filter by geographic level + user wait for option to be available and select it css:select[id="filters-form-geographic-level"] + ... Local Authority District + + user checks page contains button Local Authority District + user checks testid element contains total-results 1 data set + + user clicks button Show more details + user checks testid element contains Geographic levels-value Local authority district + +Remove geographic level filter + user clicks button Local Authority District + + user checks page does not contain button Local Authority District + user checks selected option label id:filters-form-geographic-level All + + user checks page contains button ${PUPILS_AND_SCHOOLS_THEME_TITLE} + user checks page contains button ${PUPIL_ABSENCE_PUBLICATION_TITLE} + Reset all filters user clicks element id:searchForm-search user presses keys pupil