From 2c5f21c086fb58f128ee6eaa573b6a1affa677ee Mon Sep 17 00:00:00 2001 From: Tim Haasdyk Date: Thu, 30 Nov 2023 12:49:12 +0100 Subject: [PATCH 01/18] Add startup probe to hg container so otel initializes --- deployment/base/hg-deployment.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/deployment/base/hg-deployment.yaml b/deployment/base/hg-deployment.yaml index a16a31b3e..4d1b55ebc 100644 --- a/deployment/base/hg-deployment.yaml +++ b/deployment/base/hg-deployment.yaml @@ -65,6 +65,13 @@ spec: ports: - containerPort: 8088 + startupProbe: + httpGet: + path: / + port: 8088 + failureThreshold: 30 + periodSeconds: 10 + volumeMounts: - name: repos mountPath: /var/hg/repos From 94a7f616fcefe508648c896bb83a735319c5e628 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Wed, 6 Dec 2023 14:57:06 +0700 Subject: [PATCH 02/18] escape icontains search string so we can search for first_last email patterns and it matches on _ instead of treating that as a single char wildcard. #451 --- ...ngDeterministicInvariantContainsHandler.cs | 26 ++++++++++--------- .../LexBoxApi/GraphQL/GraphQlSetupKernel.cs | 13 ++-------- 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/backend/LexBoxApi/GraphQL/CustomFilters/QueryableStringDeterministicInvariantContainsHandler.cs b/backend/LexBoxApi/GraphQL/CustomFilters/QueryableStringDeterministicInvariantContainsHandler.cs index 6747f7439..532d69f04 100644 --- a/backend/LexBoxApi/GraphQL/CustomFilters/QueryableStringDeterministicInvariantContainsHandler.cs +++ b/backend/LexBoxApi/GraphQL/CustomFilters/QueryableStringDeterministicInvariantContainsHandler.cs @@ -1,5 +1,6 @@ using System.Linq.Expressions; using System.Reflection; +using System.Text.RegularExpressions; using HotChocolate.Data.Filters; using HotChocolate.Data.Filters.Expressions; using HotChocolate.Language; @@ -12,9 +13,10 @@ namespace LexBoxApi.GraphQL.CustomFilters; /// Postgres doesn't support substring comparisons on nondeterministic collations, so we offer a /// case insensitive filter that explicitly uses a deterministic collation (und-x-icu) instead. /// -public class QueryableStringDeterministicInvariantContainsHandler : QueryableStringOperationHandler +public class QueryableStringDeterministicInvariantContainsHandler(InputParser inputParser) + : QueryableStringOperationHandler(inputParser) { - private static readonly MethodInfo Ilike = ((Func)NpgsqlDbFunctionsExtensions.ILike).Method; + private static readonly MethodInfo Ilike = ((Func)NpgsqlDbFunctionsExtensions.ILike).Method; private static readonly MethodInfo Collate = ((Func)RelationalDbFunctionsExtensions.Collate).Method; private static readonly ConstantExpression EfFunctions = Expression.Constant(EF.Functions); @@ -25,22 +27,19 @@ static QueryableStringDeterministicInvariantContainsHandler() } protected override int Operation => CustomFilterOperations.IContains; - - public QueryableStringDeterministicInvariantContainsHandler(InputParser inputParser) - : base(inputParser) - { - } + private static readonly Regex EscapeLikePatternRegex = new(@"([\\_%])", RegexOptions.Compiled); public override Expression HandleOperation( QueryableFilterContext context, IFilterOperationField field, IValueNode value, - object? search) + object? searchObject) { - if (search is not string) - throw new InvalidOperationException($"Expected {nameof(QueryableStringDeterministicInvariantContainsHandler)} to be called with a string, but was {search}."); + if (searchObject is not string search) + throw new InvalidOperationException($"Expected {nameof(QueryableStringDeterministicInvariantContainsHandler)} to be called with a string, but was {searchObject}."); - var pattern = $"%{search}%"; + var escapedString = EscapeLikePatternRegex.Replace(search, @"\$1"); + var pattern = $"%{escapedString}%"; var property = context.GetInstance(); var collatedValueExpression = Expression.Call( @@ -53,6 +52,8 @@ public override Expression HandleOperation( Expression.Constant("und-x-icu") ); + //this is a bit of a hack to make sure that the pattern is interpreted as a query parameter instead of a string constant. This means queries will be cached. + Expression> lambda = () => pattern; // property != null && EF.Functions.ILike(EF.Functions.Collate(property, "und-x-icu"), "%search%") return Expression.AndAlso( Expression.NotEqual(property, Expression.Constant(null, typeof(object))), @@ -61,7 +62,8 @@ public override Expression HandleOperation( Ilike, EfFunctions, collatedValueExpression, - Expression.Constant(pattern) + lambda.Body, + Expression.Constant(@"\") ) ); } diff --git a/backend/LexBoxApi/GraphQL/GraphQlSetupKernel.cs b/backend/LexBoxApi/GraphQL/GraphQlSetupKernel.cs index 0121e0e92..0f79ccb4e 100644 --- a/backend/LexBoxApi/GraphQL/GraphQlSetupKernel.cs +++ b/backend/LexBoxApi/GraphQL/GraphQlSetupKernel.cs @@ -40,17 +40,8 @@ public static void AddLexGraphQL(this IServiceCollection services, IHostEnvironm descriptor.AddDefaults(); descriptor.AddDeterministicInvariantContainsFilter(); }) - .AddProjections(descriptor => - { - descriptor.Provider(new QueryableProjectionProvider(providerDescriptor => - { - //does not work because hot chocolate wants to make this as the select `p => new project { userCount = p.usercount}` - // which doesn't work when using projectable because the field needs to be write only - //shelving it for now - providerDescriptor.RegisterFieldHandler(); - providerDescriptor.AddDefaults(); - })); - }).SetPagingOptions(new () + .AddProjections() + .SetPagingOptions(new () { DefaultPageSize = 100, MaxPageSize = 1000, From 3a0e0651543d50ef92ab0f2ddccc00fc4bb52356 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Wed, 6 Dec 2023 15:01:13 +0700 Subject: [PATCH 03/18] remove prod domain references #420 --- .github/workflows/release-pipeline.yaml | 2 +- .../production/ingress-config-prod.yaml | 45 +------------------ .../production/lexbox-deployment.patch.yaml | 2 +- 3 files changed, 4 insertions(+), 45 deletions(-) diff --git a/.github/workflows/release-pipeline.yaml b/.github/workflows/release-pipeline.yaml index bab9609dc..59cbfd116 100644 --- a/.github/workflows/release-pipeline.yaml +++ b/.github/workflows/release-pipeline.yaml @@ -75,5 +75,5 @@ jobs: version: ${{ needs.set-version.outputs.version }} image: 'ghcr.io/sillsdev/lexbox-*' k8s-environment: production - deploy-domain: prod.languagedepot.org + deploy-domain: languagedepot.org diff --git a/deployment/production/ingress-config-prod.yaml b/deployment/production/ingress-config-prod.yaml index 2d2c30de9..ae155b2b3 100644 --- a/deployment/production/ingress-config-prod.yaml +++ b/deployment/production/ingress-config-prod.yaml @@ -1,9 +1,9 @@ - op: replace path: /spec/rules/0/host - value: prod.languagedepot.org + value: languagedepot.org - op: replace path: /spec/rules/1/host - value: hg-prod.languageforge.org + value: hg-public.languageforge.org - op: replace path: /spec/rules/2/host value: resumable.languagedepot.org @@ -13,9 +13,7 @@ - op: replace path: /spec/tls/0/hosts value: - - prod.languagedepot.org - languagedepot.org - - hg-prod.languageforge.org - resumable.languagedepot.org - resumable.languageforge.org - hg-public.languagedepot.org @@ -37,11 +35,6 @@ name: lexbox port: name: http -- op: add - path: /spec/rules/- - value: - host: hg-public.languageforge.org - http: *rule - op: add path: /spec/rules/- value: @@ -62,37 +55,3 @@ value: host: admin.languageforge.org http: *rule -- op: add - path: /spec/rules/- - value: - host: languagedepot.org - http: - paths: - - path: /api - pathType: Prefix - backend: - service: - name: lexbox - port: - name: http - - path: /hg - pathType: Prefix - backend: - service: - name: lexbox - port: - name: http - - path: /v1/traces - pathType: Prefix - backend: - service: - name: lexbox - port: - name: otel - - path: / - pathType: Prefix - backend: - service: - name: ui - port: - name: sveltekit diff --git a/deployment/production/lexbox-deployment.patch.yaml b/deployment/production/lexbox-deployment.patch.yaml index 42c22e35d..3f903858e 100644 --- a/deployment/production/lexbox-deployment.patch.yaml +++ b/deployment/production/lexbox-deployment.patch.yaml @@ -24,7 +24,7 @@ spec: # TODO: need to parameterize this value: "Language Depot " - name: Email__BaseUrl - value: "https://prod.languagedepot.org" + value: "https://languagedepot.org" - name: HgConfig__PublicRedmineHgWebUrl value: "https://hg-public-redmine.languagedepot.org/" - name: HgConfig__PrivateRedmineHgWebUrl From 9a7ede53515af043980892fb3c063514eab5bf12 Mon Sep 17 00:00:00 2001 From: Tim Haasdyk Date: Tue, 5 Dec 2023 16:49:11 +0100 Subject: [PATCH 04/18] Standardize enum serialization to fix creating projects --- backend/LexBoxApi/Program.cs | 3 ++- backend/LexBoxApi/Services/EmailService.cs | 8 +++++++- frontend/src/lib/components/IconButton.svelte | 5 ++++- frontend/src/routes/email/+server.ts | 2 +- frontend/src/routes/email/emails.ts | 8 ++++---- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/backend/LexBoxApi/Program.cs b/backend/LexBoxApi/Program.cs index 85f8f4742..faa04be86 100644 --- a/backend/LexBoxApi/Program.cs +++ b/backend/LexBoxApi/Program.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Text.Json; using System.Text.Json.Serialization; using LexBoxApi; using LexBoxApi.Auth; @@ -50,7 +51,7 @@ options.ModelMetadataDetailsProviders.Add(new SystemTextJsonValidationMetadataProvider()); }).AddJsonOptions(options => { - options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseUpper)); }); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); diff --git a/backend/LexBoxApi/Services/EmailService.cs b/backend/LexBoxApi/Services/EmailService.cs index 59394907f..467ed437e 100644 --- a/backend/LexBoxApi/Services/EmailService.cs +++ b/backend/LexBoxApi/Services/EmailService.cs @@ -1,4 +1,6 @@ using System.Diagnostics; +using System.Text.Json; +using System.Text.Json.Serialization; using LexBoxApi.Config; using LexBoxApi.Models.Project; using LexBoxApi.Otel; @@ -114,7 +116,11 @@ private async Task RenderEmail(MimeMessage message, T parameters) where T : E var httpClient = _clientFactory.CreateClient(); httpClient.BaseAddress = new Uri("http://" + _emailConfig.EmailRenderHost); parameters.BaseUrl = _emailConfig.BaseUrl; - var response = await httpClient.PostAsJsonAsync("email", parameters); + var response = await httpClient.PostAsJsonAsync("email", parameters, new JsonSerializerOptions() + { + Converters = { new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseUpper) }, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }); response.EnsureSuccessStatusCode(); var renderResult = await response.Content.ReadFromJsonAsync(); if (renderResult is null) diff --git a/frontend/src/lib/components/IconButton.svelte b/frontend/src/lib/components/IconButton.svelte index f1b71461a..9ead2ff63 100644 --- a/frontend/src/lib/components/IconButton.svelte +++ b/frontend/src/lib/components/IconButton.svelte @@ -7,9 +7,12 @@ export let disabled = false; export let loading = false; export let style: CssClassList<'btn-success', 'btn-ghost' | 'btn-outline'> = 'btn-outline'; + export let active = false; + export let join = false; + -