Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make api to call fw headless sync #1252

Merged
merged 8 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Tiltfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ docker_build(
context='backend',
dockerfile='./backend/FwHeadless/dev.Dockerfile',
only=['.'],
ignore=['LexBoxApi'],
ignore=['LexBoxApi', '**/Mercurial', '**/MercurialExtensions'],
live_update=[
sync('backend', '/src/backend')
]
Expand Down
5 changes: 4 additions & 1 deletion backend/FwHeadless/HttpClientAuthHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
private async Task SetAuthHeader(HttpRequestMessage request, CancellationToken cancellationToken, Uri lexboxUrl)
{
var cookieContainer = new CookieContainer();
cookieContainer.Add(new Cookie(LexAuthConstants.AuthCookieName, await GetToken(cancellationToken), null, lexboxUrl.Authority));
cookieContainer.Add(new Cookie(LexAuthConstants.AuthCookieName, await GetToken(cancellationToken), null, lexboxUrl.Host)
{
Port = $"\"{lexboxUrl.Port}\""
});
request.Headers.Add("Cookie", cookieContainer.GetCookieHeader(lexboxUrl));
}

Expand Down
5 changes: 3 additions & 2 deletions backend/FwHeadless/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using FwLiteProjectSync;
using LcmCrdt;
using LcmCrdt.RemoteSync;
using LexCore.Sync;
using LexData;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.Extensions.Options;
Expand Down Expand Up @@ -51,7 +52,7 @@

app.Run();

static async Task<Results<Ok<CrdtFwdataProjectSyncService.SyncResult>, NotFound, ProblemHttpResult>> ExecuteMergeRequest(
static async Task<Results<Ok<SyncResult>, NotFound, ProblemHttpResult>> ExecuteMergeRequest(
ILogger<Program> logger,
IServiceProvider services,
SendReceiveService srService,
Expand All @@ -69,7 +70,7 @@
if (dryRun)
{
logger.LogInformation("Dry run, not actually syncing");
return TypedResults.Ok(new CrdtFwdataProjectSyncService.SyncResult(0, 0));
return TypedResults.Ok(new SyncResult(0, 0));
}

var projectCode = await projectLookupService.GetProjectCode(projectId);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Text.Json;
using FwDataMiniLcmBridge.Api;
using LcmCrdt;
using LexCore.Sync;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MiniLcm;
Expand All @@ -13,8 +14,6 @@ namespace FwLiteProjectSync;

public class CrdtFwdataProjectSyncService(IOptions<LcmCrdtConfig> lcmCrdtConfig, MiniLcmImport miniLcmImport, ILogger<CrdtFwdataProjectSyncService> logger)
{
public record SyncResult(int CrdtChanges, int FwdataChanges);

public async Task<SyncResult> Sync(IMiniLcmApi crdtApi, FwDataMiniLcmApi fwdataApi, bool dryRun = false)
{
if (crdtApi is CrdtMiniLcmApi crdt && crdt.ProjectData.FwProjectId != fwdataApi.ProjectId)
Expand Down
18 changes: 16 additions & 2 deletions backend/LexBoxApi/Controllers/CrdtController.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
using System.Text.Json.Serialization;
using LexBoxApi.Auth;
using SIL.Harmony.Core;
using LexBoxApi.Auth;
using LexBoxApi.Auth.Attributes;
using LexBoxApi.Hub;
using LexBoxApi.Services;
using LexCore.Entities;
using LexCore.ServiceInterfaces;
using LexCore.Sync;
using LexData;
using Microsoft.AspNetCore.Http.Timeouts;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
Expand All @@ -21,7 +24,8 @@ public class CrdtController(
IHubContext<CrdtProjectChangeHub, IProjectChangeListener> hubContext,
IPermissionService permissionService,
LoggedInContext loggedInContext,
ProjectService projectService) : ControllerBase
ProjectService projectService,
FwHeadlessClient fwHeadlessClient) : ControllerBase
{
private DbSet<ServerCommit> ServerCommits => dbContext.Set<ServerCommit>();

Expand Down Expand Up @@ -90,4 +94,14 @@ public async Task<ActionResult<Guid>> GetProjectId(string code)

return Ok(projectId);
}

[HttpPost("sync/{projectId}")]
[AdminRequired]
hahn-kev marked this conversation as resolved.
Show resolved Hide resolved
[RequestTimeout(300_000)]//5 minutes
public async Task<ActionResult<SyncResult?>> ExecuteMerge(Guid projectId)
{
var result = await fwHeadlessClient.CrdtSync(projectId);
if (result is null) return Problem("Failed to sync CRDT");
return result;
}
}
1 change: 1 addition & 0 deletions backend/LexBoxApi/LexBoxApi.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="9.0.0" />
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.0.2" />
<PackageReference Include="Nito.AsyncEx.Coordination" Version="5.1.2" />
<PackageReference Include="Npgsql.OpenTelemetry" Version="8.0.3" />
Expand Down
3 changes: 3 additions & 0 deletions backend/LexBoxApi/LexBoxKernel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ public static void AddLexBoxApi(this IServiceCollection services,
.ValidateDataAnnotations()
.ValidateOnStart();
services.AddHttpClient();
services.AddServiceDiscovery();
services.AddHttpClient<FwHeadlessClient>(client => client.BaseAddress = new ("http://fwHeadless"))
.AddServiceDiscovery();//service discovery means that we lookup the hostname in Services__fwHeadless__http in config
services.AddHttpContextAccessor();
services.AddMemoryCache();
services.AddScoped<LoggedInContext>();
Expand Down
19 changes: 19 additions & 0 deletions backend/LexBoxApi/Services/FwHeadlessClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using LexCore.Sync;

namespace LexBoxApi.Services;

public class FwHeadlessClient(HttpClient httpClient, ILogger<FwHeadlessClient> logger)
{
public async Task<SyncResult?> CrdtSync(Guid projectId)
{
var response = await httpClient.PostAsync($"/api/crdt-sync?projectId={projectId}", null);
if (response.IsSuccessStatusCode)
return await response.Content.ReadFromJsonAsync<SyncResult>();
logger.LogError("Failed to sync CRDT: {StatusCode} {StatusDescription}, projectId: {ProjectId}, response: {Response}",
response.StatusCode,
response.ReasonPhrase,
projectId,
await response.Content.ReadAsStringAsync());
return null;
}
}
7 changes: 7 additions & 0 deletions backend/LexBoxApi/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,12 @@
"From": "Lexbox <[email protected]>",
"EmailRenderHost": "localhost:3000",
"BaseUrl": "http://localhost:3000"
},
"Services": {
"fwHeadless": {
"http": [
"localhost:5275"
]
}
}
}
5 changes: 5 additions & 0 deletions backend/LexBoxApi/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,10 @@
},
"Email": {
"CreateProjectEmailDestination": "[email protected]"
},
"Services": {
"fwHeadless": {
"http": ["fw-headless"]
}
}
}
3 changes: 3 additions & 0 deletions backend/LexCore/Sync/SyncResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace LexCore.Sync;

public record SyncResult(int CrdtChanges, int FwdataChanges);
1 change: 1 addition & 0 deletions backend/LexData/SeedingData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
LexBoxDbContext lexBoxDbContext,
IOptions<DbConfig> dbConfig,
IHostEnvironment environment,
ILogger<SeedingData> logger,

Check warning on line 18 in backend/LexData/SeedingData.cs

View workflow job for this annotation

GitHub Actions / Build API / publish-api

Parameter 'logger' is unread.

Check warning on line 18 in backend/LexData/SeedingData.cs

View workflow job for this annotation

GitHub Actions / Build API / publish-api

Parameter 'logger' is unread.

Check warning on line 18 in backend/LexData/SeedingData.cs

View workflow job for this annotation

GitHub Actions / Build FwHeadless / publish-fw-headless

Parameter 'logger' is unread.
IOpenIddictApplicationManager? applicationManager = null)
{
public static readonly Guid TestAdminId = new("cf430ec9-e721-450a-b6a1-9a853212590b");
Expand Down Expand Up @@ -156,6 +156,7 @@
Description = "Eastern Lawa project",
Code = "elawa-dev-flex",
Type = ProjectType.FLEx,
FlexProjectMetadata = new(),
ProjectOrigin = ProjectMigrationStatus.Migrated,
LastCommit = DateTimeOffset.UtcNow,
RetentionPolicy = RetentionPolicy.Dev,
Expand Down
3 changes: 3 additions & 0 deletions deployment/base/lexbox-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ spec:
value: /tmp/tus-test-upload
- name: Tus__ResetUploadPath
value: /tmp/tus-reset-upload
- name: Services__fwHeadless__http__0
value: fw-headless


- name: otel-collector
image: otel/opentelemetry-collector-contrib:0.101.0
Expand Down
7 changes: 5 additions & 2 deletions frontend/src/lib/forms/Button.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@
export let type: undefined | 'submit' = undefined;
export let size: undefined | 'btn-sm' = undefined;
export let disabled = false;
export let customLoader = false;
</script>

<!-- https://daisyui.com/components/button -->
<button on:click {...$$restProps} class="btn whitespace-nowrap {variant ?? ''} {$$restProps.class ?? ''} {size ?? ''}" {type}
class:btn-outline={outline}
disabled={disabled && !loading}
class:pointer-events-none={loading}>
<Loader {loading} />
class:pointer-events-none={loading || $$restProps.class?.includes('pointer-events-none')}>
{#if !customLoader}
<Loader {loading} />
{/if}
<slot />
</button>
4 changes: 3 additions & 1 deletion frontend/src/lib/icons/Icon.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@
export let size: IconSize = 'text-lg';
export let color: `text-${string}` | undefined = undefined;
export let pale = false;
export let spin = false;
export let spinReverse = false;
// For pixel perfect text alignment, because the svgs often contain vertical white-space
export let y: string | undefined = undefined;

$: transform = y ? `translateY(${y})` : '';
</script>

{#if icon}
<span class="{icon} {size} {color ?? ''} shrink-0" class:pale style:transform />
<span class="{icon} {size} {color ?? ''} shrink-0 {spinReverse ? 'transform rotate-180' : ''}" class:pale style:transform class:animate-spin={spin} />
{/if}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import { onMount } from 'svelte';
import { getSearchParamValues } from '$lib/util/query-params';
import FlexModelVersionText from '$lib/components/Projects/FlexModelVersionText.svelte';
import CrdtSyncButton from './CrdtSyncButton.svelte';

export let data: PageData;
$: user = data.user;
Expand Down Expand Up @@ -312,6 +313,7 @@
</a>
{/if}
{#if project.type === ProjectType.FlEx && $isDev}
<CrdtSyncButton projectId={project.id} />
<OpenInFlexModal bind:this={openInFlexModal} {project}/>
<OpenInFlexButton projectId={project.id} on:click={openInFlexModal.open}/>
{:else}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<script lang="ts">
import {Button} from '$lib/forms';
import {Icon} from '$lib/icons';
import {useNotifications} from '$lib/notify';

export let projectId: string;

const {notifySuccess, notifyWarning} = useNotifications();

let syncing = false;

async function triggerSync(): Promise<void> {
syncing = true;
try {
const response = await fetch(`/api/crdt/sync/${projectId}`, {
method: 'POST',
});

if (response.ok) {
const { crdtChanges, fwdataChanges } = await response.json();
notifySuccess(`Synced successfully (${fwdataChanges} FwData changes. ${crdtChanges} CRDT changes)`);
} else {
const error = `Failed to sync: ${response.statusText} (${response.status})`;
notifyWarning(error);
console.error(error, await response.text());
}
} finally {
syncing = false;
}
}
</script>

<Button
variant="btn-primary"
class="gap-1"
on:click={triggerSync}
loading={syncing}
customLoader
>
FwData
<Icon icon="i-mdi-sync" spin={syncing} spinReverse />
CRDT
</Button>
Loading