From e7d5aec255fd6044019f6e427a986b73a3ba5410 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Tue, 15 Oct 2024 13:56:38 +0700 Subject: [PATCH] Get FLEx model version from repo and store in metadata (#1082) Project page now shows the version number of FieldWorks that was used to create the project, based on the model version appearing at the tip of the hg repo. (A tooltip also shows the raw model number, e.g. 7000072, when hovered over). This is NOT automatically updated on hg push, as we expect this to rarely ever change. --- backend/LexBoxApi/GraphQL/ProjectMutations.cs | 17 + backend/LexBoxApi/Services/HgService.cs | 8 + backend/LexBoxApi/Services/ProjectService.cs | 12 + .../LexCore/Entities/FlexProjectMetadata.cs | 1 + .../LexCore/ServiceInterfaces/IHgService.cs | 1 + ..._AddModelVersionToFlexMetadata.Designer.cs | 1402 +++++++++++++++++ ...926093530_AddModelVersionToFlexMetadata.cs | 28 + .../LexBoxDbContextModelSnapshot.cs | 3 + frontend/schema.graphql | 15 + .../Projects/FlexModelVersionText.svelte | 31 + frontend/src/lib/i18n/locales/en.json | 1 + .../project/[project_code]/+page.svelte | 25 + .../project/[project_code]/+page.ts | 28 + hgweb/command-runner.sh | 6 +- 14 files changed, 1577 insertions(+), 1 deletion(-) create mode 100644 backend/LexData/Migrations/20240926093530_AddModelVersionToFlexMetadata.Designer.cs create mode 100644 backend/LexData/Migrations/20240926093530_AddModelVersionToFlexMetadata.cs create mode 100644 frontend/src/lib/components/Projects/FlexModelVersionText.svelte diff --git a/backend/LexBoxApi/GraphQL/ProjectMutations.cs b/backend/LexBoxApi/GraphQL/ProjectMutations.cs index 7e7cd16af..3ceeeb163 100644 --- a/backend/LexBoxApi/GraphQL/ProjectMutations.cs +++ b/backend/LexBoxApi/GraphQL/ProjectMutations.cs @@ -412,6 +412,23 @@ public async Task> UpdateLangProjectId(string code, return dbContext.Projects.Where(p => p.Id == projectId); } + [Error] + [Error] + [Error] + [UseMutationConvention] + [UseFirstOrDefault] + [UseProjection] + public async Task> UpdateFLExModelVersion(string code, + IPermissionService permissionService, + [Service] ProjectService projectService, + LexBoxDbContext dbContext) + { + var projectId = await projectService.LookupProjectId(code); + await permissionService.AssertCanManageProject(projectId); + await projectService.UpdateFLExModelVersion(projectId); + return dbContext.Projects.Where(p => p.Id == projectId); + } + [Error] [Error] [UseMutationConvention] diff --git a/backend/LexBoxApi/Services/HgService.cs b/backend/LexBoxApi/Services/HgService.cs index b3d3f956c..6a48f3c16 100644 --- a/backend/LexBoxApi/Services/HgService.cs +++ b/backend/LexBoxApi/Services/HgService.cs @@ -267,6 +267,14 @@ private string[] GetWsList(System.Xml.XmlElement root, string tagName) return null; } + public async Task GetModelVersionOfFlexProject(ProjectCode code, CancellationToken token = default) + { + var result = await ExecuteHgCommandServerCommand(code, "flexmodelversion", token); + var text = await result.ReadAsStringAsync(token); + var json = JsonDocument.Parse(text); + return json.RootElement.GetProperty("modelversion").GetInt32(); + } + public Task RevertRepo(ProjectCode code, string revHash) { throw new NotImplementedException(); diff --git a/backend/LexBoxApi/Services/ProjectService.cs b/backend/LexBoxApi/Services/ProjectService.cs index 363db5641..6c89ea5a4 100644 --- a/backend/LexBoxApi/Services/ProjectService.cs +++ b/backend/LexBoxApi/Services/ProjectService.cs @@ -81,6 +81,18 @@ public async Task UpdateProjectLangProjectId(Guid projectId) await dbContext.SaveChangesAsync(); } + public async Task UpdateFLExModelVersion(Guid projectId) + { + var project = await dbContext.Projects.FindAsync(projectId); + if (project is null || project.Type != ProjectType.FLEx) return; + await dbContext.Entry(project).Reference(p => p.FlexProjectMetadata).LoadAsync(); + var modelVersion = await hgService.GetModelVersionOfFlexProject(project.Code); + if (modelVersion is null) return; + project.FlexProjectMetadata ??= new FlexProjectMetadata(); + project.FlexProjectMetadata.FlexModelVersion = modelVersion; + await dbContext.SaveChangesAsync(); + } + public async Task CreateDraftProject(CreateProjectInput input) { // No need for a transaction if we're just saving a single item diff --git a/backend/LexCore/Entities/FlexProjectMetadata.cs b/backend/LexCore/Entities/FlexProjectMetadata.cs index 9c5ba9bb3..4ef16a564 100644 --- a/backend/LexCore/Entities/FlexProjectMetadata.cs +++ b/backend/LexCore/Entities/FlexProjectMetadata.cs @@ -10,6 +10,7 @@ public class FlexProjectMetadata /// public Guid? LangProjectId { get; set; } public ProjectWritingSystems? WritingSystems { get; set; } + public int? FlexModelVersion { get; set; } } public class ProjectWritingSystems diff --git a/backend/LexCore/ServiceInterfaces/IHgService.cs b/backend/LexCore/ServiceInterfaces/IHgService.cs index df2426bde..488e59fa9 100644 --- a/backend/LexCore/ServiceInterfaces/IHgService.cs +++ b/backend/LexCore/ServiceInterfaces/IHgService.cs @@ -13,6 +13,7 @@ public interface IHgService Task SoftDeleteRepo(ProjectCode code, string deletedRepoSuffix); Task GetProjectWritingSystems(ProjectCode code, CancellationToken token = default); Task GetProjectIdOfFlexProject(ProjectCode code, CancellationToken token = default); + Task GetModelVersionOfFlexProject(ProjectCode code, CancellationToken token = default); BackupExecutor? BackupRepo(ProjectCode code); Task ResetRepo(ProjectCode code); Task FinishReset(ProjectCode code, Stream zipFile); diff --git a/backend/LexData/Migrations/20240926093530_AddModelVersionToFlexMetadata.Designer.cs b/backend/LexData/Migrations/20240926093530_AddModelVersionToFlexMetadata.Designer.cs new file mode 100644 index 000000000..2201989b0 --- /dev/null +++ b/backend/LexData/Migrations/20240926093530_AddModelVersionToFlexMetadata.Designer.cs @@ -0,0 +1,1402 @@ +// +using System; +using System.Collections.Generic; +using LexData; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace LexData.Migrations +{ + [DbContext(typeof(LexBoxDbContext))] + [Migration("20240926093530_AddModelVersionToFlexMetadata")] + partial class AddModelVersionToFlexMetadata + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:CollationDefinition:case_insensitive", "und-u-ks-level2,und-u-ks-level2,icu,False") + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzBlobTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("BlobData") + .HasColumnType("bytea") + .HasColumnName("blob_data"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_blob_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCalendar", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("CalendarName") + .HasColumnType("text") + .HasColumnName("calendar_name"); + + b.Property("Calendar") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("calendar"); + + b.HasKey("SchedulerName", "CalendarName"); + + b.ToTable("qrtz_calendars", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCronTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("CronExpression") + .IsRequired() + .HasColumnType("text") + .HasColumnName("cron_expression"); + + b.Property("TimeZoneId") + .HasColumnType("text") + .HasColumnName("time_zone_id"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_cron_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzFiredTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("EntryId") + .HasColumnType("text") + .HasColumnName("entry_id"); + + b.Property("FiredTime") + .HasColumnType("bigint") + .HasColumnName("fired_time"); + + b.Property("InstanceName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("instance_name"); + + b.Property("IsNonConcurrent") + .HasColumnType("bool") + .HasColumnName("is_nonconcurrent"); + + b.Property("JobGroup") + .HasColumnType("text") + .HasColumnName("job_group"); + + b.Property("JobName") + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("RequestsRecovery") + .HasColumnType("bool") + .HasColumnName("requests_recovery"); + + b.Property("ScheduledTime") + .HasColumnType("bigint") + .HasColumnName("sched_time"); + + b.Property("State") + .IsRequired() + .HasColumnType("text") + .HasColumnName("state"); + + b.Property("TriggerGroup") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("TriggerName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.HasKey("SchedulerName", "EntryId"); + + b.HasIndex("InstanceName") + .HasDatabaseName("idx_qrtz_ft_trig_inst_name"); + + b.HasIndex("JobGroup") + .HasDatabaseName("idx_qrtz_ft_job_group"); + + b.HasIndex("JobName") + .HasDatabaseName("idx_qrtz_ft_job_name"); + + b.HasIndex("RequestsRecovery") + .HasDatabaseName("idx_qrtz_ft_job_req_recovery"); + + b.HasIndex("TriggerGroup") + .HasDatabaseName("idx_qrtz_ft_trig_group"); + + b.HasIndex("TriggerName") + .HasDatabaseName("idx_qrtz_ft_trig_name"); + + b.HasIndex("SchedulerName", "TriggerName", "TriggerGroup") + .HasDatabaseName("idx_qrtz_ft_trig_nm_gp"); + + b.ToTable("qrtz_fired_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("JobName") + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("JobGroup") + .HasColumnType("text") + .HasColumnName("job_group"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("IsDurable") + .HasColumnType("bool") + .HasColumnName("is_durable"); + + b.Property("IsNonConcurrent") + .HasColumnType("bool") + .HasColumnName("is_nonconcurrent"); + + b.Property("IsUpdateData") + .HasColumnType("bool") + .HasColumnName("is_update_data"); + + b.Property("JobClassName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_class_name"); + + b.Property("JobData") + .HasColumnType("bytea") + .HasColumnName("job_data"); + + b.Property("RequestsRecovery") + .HasColumnType("bool") + .HasColumnName("requests_recovery"); + + b.HasKey("SchedulerName", "JobName", "JobGroup"); + + b.HasIndex("RequestsRecovery") + .HasDatabaseName("idx_qrtz_j_req_recovery"); + + b.ToTable("qrtz_job_details", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzLock", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("LockName") + .HasColumnType("text") + .HasColumnName("lock_name"); + + b.HasKey("SchedulerName", "LockName"); + + b.ToTable("qrtz_locks", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzPausedTriggerGroup", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.HasKey("SchedulerName", "TriggerGroup"); + + b.ToTable("qrtz_paused_trigger_grps", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSchedulerState", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("InstanceName") + .HasColumnType("text") + .HasColumnName("instance_name"); + + b.Property("CheckInInterval") + .HasColumnType("bigint") + .HasColumnName("checkin_interval"); + + b.Property("LastCheckInTime") + .HasColumnType("bigint") + .HasColumnName("last_checkin_time"); + + b.HasKey("SchedulerName", "InstanceName"); + + b.ToTable("qrtz_scheduler_state", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimplePropertyTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("BooleanProperty1") + .HasColumnType("bool") + .HasColumnName("bool_prop_1"); + + b.Property("BooleanProperty2") + .HasColumnType("bool") + .HasColumnName("bool_prop_2"); + + b.Property("DecimalProperty1") + .HasColumnType("numeric") + .HasColumnName("dec_prop_1"); + + b.Property("DecimalProperty2") + .HasColumnType("numeric") + .HasColumnName("dec_prop_2"); + + b.Property("IntegerProperty1") + .HasColumnType("integer") + .HasColumnName("int_prop_1"); + + b.Property("IntegerProperty2") + .HasColumnType("integer") + .HasColumnName("int_prop_2"); + + b.Property("LongProperty1") + .HasColumnType("bigint") + .HasColumnName("long_prop_1"); + + b.Property("LongProperty2") + .HasColumnType("bigint") + .HasColumnName("long_prop_2"); + + b.Property("StringProperty1") + .HasColumnType("text") + .HasColumnName("str_prop_1"); + + b.Property("StringProperty2") + .HasColumnType("text") + .HasColumnName("str_prop_2"); + + b.Property("StringProperty3") + .HasColumnType("text") + .HasColumnName("str_prop_3"); + + b.Property("TimeZoneId") + .HasColumnType("text") + .HasColumnName("time_zone_id"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_simprop_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimpleTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("RepeatCount") + .HasColumnType("bigint") + .HasColumnName("repeat_count"); + + b.Property("RepeatInterval") + .HasColumnType("bigint") + .HasColumnName("repeat_interval"); + + b.Property("TimesTriggered") + .HasColumnType("bigint") + .HasColumnName("times_triggered"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.ToTable("qrtz_simple_triggers", "quartz"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => + { + b.Property("SchedulerName") + .HasColumnType("text") + .HasColumnName("sched_name"); + + b.Property("TriggerName") + .HasColumnType("text") + .HasColumnName("trigger_name"); + + b.Property("TriggerGroup") + .HasColumnType("text") + .HasColumnName("trigger_group"); + + b.Property("CalendarName") + .HasColumnType("text") + .HasColumnName("calendar_name"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("EndTime") + .HasColumnType("bigint") + .HasColumnName("end_time"); + + b.Property("JobData") + .HasColumnType("bytea") + .HasColumnName("job_data"); + + b.Property("JobGroup") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_group"); + + b.Property("JobName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("MisfireInstruction") + .HasColumnType("smallint") + .HasColumnName("misfire_instr"); + + b.Property("NextFireTime") + .HasColumnType("bigint") + .HasColumnName("next_fire_time"); + + b.Property("PreviousFireTime") + .HasColumnType("bigint") + .HasColumnName("prev_fire_time"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("StartTime") + .HasColumnType("bigint") + .HasColumnName("start_time"); + + b.Property("TriggerState") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_state"); + + b.Property("TriggerType") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trigger_type"); + + b.HasKey("SchedulerName", "TriggerName", "TriggerGroup"); + + b.HasIndex("NextFireTime") + .HasDatabaseName("idx_qrtz_t_next_fire_time"); + + b.HasIndex("TriggerState") + .HasDatabaseName("idx_qrtz_t_state"); + + b.HasIndex("NextFireTime", "TriggerState") + .HasDatabaseName("idx_qrtz_t_nft_st"); + + b.HasIndex("SchedulerName", "JobName", "JobGroup"); + + b.ToTable("qrtz_triggers", "quartz"); + }); + + modelBuilder.Entity("LexCore.Entities.DraftProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsConfidential") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrgId") + .HasColumnType("uuid"); + + b.Property("ProjectManagerId") + .HasColumnType("uuid"); + + b.Property("RetentionPolicy") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("ProjectManagerId"); + + b.ToTable("DraftProjects"); + }); + + modelBuilder.Entity("LexCore.Entities.FlexProjectMetadata", b => + { + b.Property("ProjectId") + .HasColumnType("uuid"); + + b.Property("FlexModelVersion") + .HasColumnType("integer"); + + b.Property("LangProjectId") + .HasColumnType("uuid"); + + b.Property("LexEntryCount") + .HasColumnType("integer"); + + b.HasKey("ProjectId"); + + b.ToTable("FlexProjectMetadata"); + }); + + modelBuilder.Entity("LexCore.Entities.OrgMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("OrgId") + .HasColumnType("uuid"); + + b.Property("Role") + .HasColumnType("integer"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrgId"); + + b.HasIndex("UserId", "OrgId") + .IsUnique(); + + b.ToTable("OrgMembers", (string)null); + }); + + modelBuilder.Entity("LexCore.Entities.OrgProjects", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("OrgId") + .HasColumnType("uuid"); + + b.Property("ProjectId") + .HasColumnType("uuid"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("OrgId", "ProjectId") + .IsUnique(); + + b.ToTable("OrgProjects"); + }); + + modelBuilder.Entity("LexCore.Entities.Organization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Orgs", (string)null); + }); + + modelBuilder.Entity("LexCore.Entities.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("IsConfidential") + .HasColumnType("boolean"); + + b.Property("LastCommit") + .HasColumnType("timestamp with time zone"); + + b.Property("MigratedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ParentId") + .HasColumnType("uuid"); + + b.Property("ProjectOrigin") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(1); + + b.Property("ResetStatus") + .HasColumnType("integer"); + + b.Property("RetentionPolicy") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("ParentId"); + + b.ToTable("Projects"); + }); + + modelBuilder.Entity("LexCore.Entities.ProjectUsers", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("ProjectId") + .HasColumnType("uuid"); + + b.Property("Role") + .HasColumnType("integer"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UserId", "ProjectId") + .IsUnique(); + + b.ToTable("ProjectUsers"); + }); + + modelBuilder.Entity("LexCore.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CanCreateProjects") + .HasColumnType("boolean"); + + b.Property("CreatedById") + .HasColumnType("uuid"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Email") + .HasColumnType("text") + .UseCollation("case_insensitive"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("GoogleId") + .HasColumnType("text"); + + b.Property("IsAdmin") + .HasColumnType("boolean"); + + b.Property("LastActive") + .HasColumnType("timestamp with time zone"); + + b.Property("LocalizationCode") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("en"); + + b.Property("Locked") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + + b.Property("PasswordStrength") + .HasColumnType("integer"); + + b.Property("Salt") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("Username") + .HasColumnType("text") + .UseCollation("case_insensitive"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ClientId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClientSecret") + .HasColumnType("text"); + + b.Property("ClientType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConsentType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("JsonWebKeySet") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("PostLogoutRedirectUris") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedirectUris") + .HasColumnType("text"); + + b.Property("Requirements") + .HasColumnType("text"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.ToTable("OpenIddictApplications", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Scopes") + .HasColumnType("text"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictAuthorizations", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Descriptions") + .HasColumnType("text"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Resources") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OpenIddictScopes", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("AuthorizationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Payload") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedemptionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReferenceId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("AuthorizationId"); + + b.HasIndex("ReferenceId") + .IsUnique(); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictTokens", (string)null); + }); + + modelBuilder.Entity("SIL.Harmony.Core.ServerCommit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("Metadata") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectId") + .HasColumnType("uuid"); + + b.ComplexProperty>("HybridDateTime", "SIL.Harmony.Core.ServerCommit.HybridDateTime#HybridDateTime", b1 => + { + b1.IsRequired(); + + b1.Property("Counter") + .HasColumnType("bigint"); + + b1.Property("DateTime") + .HasColumnType("timestamp with time zone"); + }); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.ToTable("CrdtCommits", (string)null); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzBlobTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("BlobTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzCronTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("CronTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimplePropertyTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("SimplePropertyTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzSimpleTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", "Trigger") + .WithMany("SimpleTriggers") + .HasForeignKey("SchedulerName", "TriggerName", "TriggerGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Trigger"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => + { + b.HasOne("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", "JobDetail") + .WithMany("Triggers") + .HasForeignKey("SchedulerName", "JobName", "JobGroup") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("JobDetail"); + }); + + modelBuilder.Entity("LexCore.Entities.DraftProject", b => + { + b.HasOne("LexCore.Entities.User", "ProjectManager") + .WithMany() + .HasForeignKey("ProjectManagerId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("ProjectManager"); + }); + + modelBuilder.Entity("LexCore.Entities.FlexProjectMetadata", b => + { + b.HasOne("LexCore.Entities.Project", null) + .WithOne("FlexProjectMetadata") + .HasForeignKey("LexCore.Entities.FlexProjectMetadata", "ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("LexCore.Entities.ProjectWritingSystems", "WritingSystems", b1 => + { + b1.Property("FlexProjectMetadataProjectId") + .HasColumnType("uuid"); + + b1.HasKey("FlexProjectMetadataProjectId"); + + b1.ToTable("FlexProjectMetadata"); + + b1.ToJson("WritingSystems"); + + b1.WithOwner() + .HasForeignKey("FlexProjectMetadataProjectId"); + + b1.OwnsMany("LexCore.Entities.FLExWsId", "AnalysisWss", b2 => + { + b2.Property("ProjectWritingSystemsFlexProjectMetadataProjectId") + .HasColumnType("uuid"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + b2.Property("IsActive") + .HasColumnType("boolean"); + + b2.Property("IsDefault") + .HasColumnType("boolean"); + + b2.Property("Tag") + .IsRequired() + .HasColumnType("text"); + + b2.HasKey("ProjectWritingSystemsFlexProjectMetadataProjectId", "Id"); + + b2.ToTable("FlexProjectMetadata"); + + b2.WithOwner() + .HasForeignKey("ProjectWritingSystemsFlexProjectMetadataProjectId"); + }); + + b1.OwnsMany("LexCore.Entities.FLExWsId", "VernacularWss", b2 => + { + b2.Property("ProjectWritingSystemsFlexProjectMetadataProjectId") + .HasColumnType("uuid"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + b2.Property("IsActive") + .HasColumnType("boolean"); + + b2.Property("IsDefault") + .HasColumnType("boolean"); + + b2.Property("Tag") + .IsRequired() + .HasColumnType("text"); + + b2.HasKey("ProjectWritingSystemsFlexProjectMetadataProjectId", "Id"); + + b2.ToTable("FlexProjectMetadata"); + + b2.WithOwner() + .HasForeignKey("ProjectWritingSystemsFlexProjectMetadataProjectId"); + }); + + b1.Navigation("AnalysisWss"); + + b1.Navigation("VernacularWss"); + }); + + b.Navigation("WritingSystems"); + }); + + modelBuilder.Entity("LexCore.Entities.OrgMember", b => + { + b.HasOne("LexCore.Entities.Organization", "Organization") + .WithMany("Members") + .HasForeignKey("OrgId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LexCore.Entities.User", "User") + .WithMany("Organizations") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LexCore.Entities.OrgProjects", b => + { + b.HasOne("LexCore.Entities.Organization", "Org") + .WithMany() + .HasForeignKey("OrgId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LexCore.Entities.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Org"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("LexCore.Entities.Project", b => + { + b.HasOne("LexCore.Entities.Project", null) + .WithMany() + .HasForeignKey("ParentId"); + }); + + modelBuilder.Entity("LexCore.Entities.ProjectUsers", b => + { + b.HasOne("LexCore.Entities.Project", "Project") + .WithMany("Users") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LexCore.Entities.User", "User") + .WithMany("Projects") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LexCore.Entities.User", b => + { + b.HasOne("LexCore.Entities.User", "CreatedBy") + .WithMany("UsersICreated") + .HasForeignKey("CreatedById") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("CreatedBy"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Authorizations") + .HasForeignKey("ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Tokens") + .HasForeignKey("ApplicationId"); + + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", "Authorization") + .WithMany("Tokens") + .HasForeignKey("AuthorizationId"); + + b.Navigation("Application"); + + b.Navigation("Authorization"); + }); + + modelBuilder.Entity("SIL.Harmony.Core.ServerCommit", b => + { + b.HasOne("LexCore.Entities.FlexProjectMetadata", null) + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsMany("SIL.Harmony.Core.ChangeEntity", "ChangeEntities", b1 => + { + b1.Property("ServerCommitId") + .HasColumnType("uuid"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + b1.Property("Change") + .HasColumnType("text"); + + b1.Property("CommitId") + .HasColumnType("uuid"); + + b1.Property("EntityId") + .HasColumnType("uuid"); + + b1.Property("Index") + .HasColumnType("integer"); + + b1.HasKey("ServerCommitId", "Id"); + + b1.ToTable("CrdtCommits"); + + b1.ToJson("ChangeEntities"); + + b1.WithOwner() + .HasForeignKey("ServerCommitId"); + }); + + b.Navigation("ChangeEntities"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzJobDetail", b => + { + b.Navigation("Triggers"); + }); + + modelBuilder.Entity("AppAny.Quartz.EntityFrameworkCore.Migrations.QuartzTrigger", b => + { + b.Navigation("BlobTriggers"); + + b.Navigation("CronTriggers"); + + b.Navigation("SimplePropertyTriggers"); + + b.Navigation("SimpleTriggers"); + }); + + modelBuilder.Entity("LexCore.Entities.Organization", b => + { + b.Navigation("Members"); + }); + + modelBuilder.Entity("LexCore.Entities.Project", b => + { + b.Navigation("FlexProjectMetadata"); + + b.Navigation("Users"); + }); + + modelBuilder.Entity("LexCore.Entities.User", b => + { + b.Navigation("Organizations"); + + b.Navigation("Projects"); + + b.Navigation("UsersICreated"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Navigation("Authorizations"); + + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Navigation("Tokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/LexData/Migrations/20240926093530_AddModelVersionToFlexMetadata.cs b/backend/LexData/Migrations/20240926093530_AddModelVersionToFlexMetadata.cs new file mode 100644 index 000000000..e498be055 --- /dev/null +++ b/backend/LexData/Migrations/20240926093530_AddModelVersionToFlexMetadata.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace LexData.Migrations +{ + /// + public partial class AddModelVersionToFlexMetadata : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "FlexModelVersion", + table: "FlexProjectMetadata", + type: "integer", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "FlexModelVersion", + table: "FlexProjectMetadata"); + } + } +} diff --git a/backend/LexData/Migrations/LexBoxDbContextModelSnapshot.cs b/backend/LexData/Migrations/LexBoxDbContextModelSnapshot.cs index f1b32c53c..daef50c7a 100644 --- a/backend/LexData/Migrations/LexBoxDbContextModelSnapshot.cs +++ b/backend/LexData/Migrations/LexBoxDbContextModelSnapshot.cs @@ -525,6 +525,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ProjectId") .HasColumnType("uuid"); + b.Property("FlexModelVersion") + .HasColumnType("integer"); + b.Property("LangProjectId") .HasColumnType("uuid"); diff --git a/frontend/schema.graphql b/frontend/schema.graphql index 95e223b6c..ca9b2b131 100644 --- a/frontend/schema.graphql +++ b/frontend/schema.graphql @@ -179,6 +179,7 @@ type FlexProjectMetadata { lexEntryCount: Int langProjectId: UUID writingSystems: ProjectWritingSystems + flexModelVersion: Int } type InvalidEmailError implements Error { @@ -246,6 +247,7 @@ type Mutation { updateProjectLexEntryCount(input: UpdateProjectLexEntryCountInput!): UpdateProjectLexEntryCountPayload! updateProjectLanguageList(input: UpdateProjectLanguageListInput!): UpdateProjectLanguageListPayload! updateLangProjectId(input: UpdateLangProjectIdInput!): UpdateLangProjectIdPayload! + updateFLExModelVersion(input: UpdateFLExModelVersionInput!): UpdateFLExModelVersionPayload! leaveProject(input: LeaveProjectInput!): LeaveProjectPayload! removeProjectMember(input: RemoveProjectMemberInput!): RemoveProjectMemberPayload! deleteDraftProject(input: DeleteDraftProjectInput!): DeleteDraftProjectPayload! @authorize(policy: "AdminRequiredPolicy") @@ -478,6 +480,11 @@ type UniqueValueError implements Error { message: String! } +type UpdateFLExModelVersionPayload { + project: Project + errors: [UpdateFLExModelVersionError!] +} + type UpdateLangProjectIdPayload { project: Project errors: [UpdateLangProjectIdError!] @@ -588,6 +595,8 @@ union SetUserLockedError = NotFoundError union SoftDeleteProjectError = NotFoundError | DbError +union UpdateFLExModelVersionError = NotFoundError | DbError | UnauthorizedAccessError + union UpdateLangProjectIdError = NotFoundError | DbError | UnauthorizedAccessError union UpdateProjectLanguageListError = NotFoundError | DbError | UnauthorizedAccessError @@ -769,12 +778,14 @@ input FlexProjectMetadataFilterInput { lexEntryCount: IntOperationFilterInput langProjectId: UuidOperationFilterInput writingSystems: ProjectWritingSystemsFilterInput + flexModelVersion: IntOperationFilterInput } input FlexProjectMetadataSortInput { projectId: SortEnumType lexEntryCount: SortEnumType langProjectId: SortEnumType + flexModelVersion: SortEnumType } input IntOperationFilterInput { @@ -1041,6 +1052,10 @@ input StringOperationFilterInput { ieq: String } +input UpdateFLExModelVersionInput { + code: String! +} + input UpdateLangProjectIdInput { code: String! } diff --git a/frontend/src/lib/components/Projects/FlexModelVersionText.svelte b/frontend/src/lib/components/Projects/FlexModelVersionText.svelte new file mode 100644 index 000000000..5adde58e2 --- /dev/null +++ b/frontend/src/lib/components/Projects/FlexModelVersionText.svelte @@ -0,0 +1,31 @@ + + + + +{fwModel} diff --git a/frontend/src/lib/i18n/locales/en.json b/frontend/src/lib/i18n/locales/en.json index 5c2a4470e..5d1fa4022 100644 --- a/frontend/src/lib/i18n/locales/en.json +++ b/frontend/src/lib/i18n/locales/en.json @@ -420,6 +420,7 @@ the [Linguistics Institute at Payap University](https://li.payap.ac.th/) in Chia "description": "Description", "created_at": "Created", "last_commit": "Last Commit", + "model_version": "Model Version", "vernacular_langs": "Vernacular writing systems", "analysis_langs": "Analysis writing systems", "organization": { diff --git a/frontend/src/routes/(authenticated)/project/[project_code]/+page.svelte b/frontend/src/routes/(authenticated)/project/[project_code]/+page.svelte index bbdb34d65..c1c248137 100644 --- a/frontend/src/routes/(authenticated)/project/[project_code]/+page.svelte +++ b/frontend/src/routes/(authenticated)/project/[project_code]/+page.svelte @@ -14,6 +14,7 @@ _deleteProjectUser, _leaveProject, _removeProjectFromOrg, + _updateFLExModelVersion, _updateProjectLanguageList, _updateProjectLexEntryCount, type ProjectUser, @@ -52,6 +53,7 @@ import WritingSystemList from '$lib/components/Projects/WritingSystemList.svelte'; import { onMount } from 'svelte'; import { getSearchParamValues } from '$lib/util/query-params'; + import FlexModelVersionText from '$lib/components/Projects/FlexModelVersionText.svelte'; export let data: PageData; $: user = data.user; @@ -70,6 +72,7 @@ }); $: lexEntryCount = project.flexProjectMetadata?.lexEntryCount; + $: flexModelVersion = project.flexProjectMetadata?.flexModelVersion; $: vernacularLangTags = project.flexProjectMetadata?.writingSystems?.vernacularWss; $: analysisLangTags = project.flexProjectMetadata?.writingSystems?.analysisWss; @@ -98,6 +101,13 @@ loadingEntryCount = false; } + let loadingModelVersion = false; + async function updateModelVersion(): Promise { + loadingModelVersion = true; + await _updateFLExModelVersion(project.code); + loadingModelVersion = false; + } + let loadingLanguageList = false; async function updateLanguageList(): Promise { loadingLanguageList = true; @@ -402,6 +412,21 @@ {/if} + {#if project.type === ProjectType.FlEx} + + + + + + + {/if} {#if project.type === ProjectType.FlEx} diff --git a/frontend/src/routes/(authenticated)/project/[project_code]/+page.ts b/frontend/src/routes/(authenticated)/project/[project_code]/+page.ts index 55b11a081..cde428c3c 100644 --- a/frontend/src/routes/(authenticated)/project/[project_code]/+page.ts +++ b/frontend/src/routes/(authenticated)/project/[project_code]/+page.ts @@ -91,6 +91,7 @@ export async function load(event: PageLoadEvent) { } flexProjectMetadata { lexEntryCount + flexModelVersion writingSystems { vernacularWss { tag @@ -304,6 +305,33 @@ export async function _updateProjectLexEntryCount(code: string): $OpResult { // $OpResult { + //language=GraphQL + const result = await getClient() + .mutation( + graphql(` + mutation UpdateFLExModelVersion($input: UpdateFLExModelVersionInput!) { + updateFLExModelVersion(input: $input) { + project { + id + flexProjectMetadata { + flexModelVersion + } + } + errors { + __typename + ... on Error { + message + } + } + } + } + `), + { input: { code } }, + ); + return result; +} + export async function _updateLangProjectId(code: string): $OpResult { //language=GraphQL const result = await getClient() diff --git a/hgweb/command-runner.sh b/hgweb/command-runner.sh index ac0e35d3a..60e6f8abf 100644 --- a/hgweb/command-runner.sh +++ b/hgweb/command-runner.sh @@ -1,7 +1,7 @@ #!/bin/bash # Define the list of allowed commands -allowed_commands=("verify" "tip" "tipdate" "wesaylexentrycount" "lexentrycount" "flexprojectid" "flexwritingsystems" "recover" "healthz" "invalidatedircache") +allowed_commands=("verify" "tip" "tipdate" "wesaylexentrycount" "lexentrycount" "flexprojectid" "flexwritingsystems" "flexmodelversion" "recover" "healthz" "invalidatedircache") # Get the project code and command name from the URL IFS='/' read -ra PATH_SEGMENTS <<< "$PATH_INFO" @@ -74,6 +74,10 @@ case $command_name in chg cat -r tip General/LanguageProject.langproj | sed -n -e '//,/<\/AnalysisWss>/p' -e '//,/<\/VernWss>/p' -e '//,/<\/CurAnalysisWss>/p' -e '//,/<\/CurVernWss>/p' -e '//,/<\/CurPronunWss>/p' ;; + flexmodelversion) + chg cat -r tip FLExProject.ModelVersion + ;; + tip) chg tip --template '{node}' ;;