Skip to content

Commit

Permalink
12993 change default name on nav button from nextback to nesteforrige (
Browse files Browse the repository at this point in the history
…#13249)

* Add syncevent for navigation button texts when adding new page

* Invalidate TextResource query for `resource.nb.json` sync success

* Reload preview when TextResourcesQuery refetches with new data

Remove accidental staged change

* Remove unnecessary using directive

* Add PreviewContextProvider to ux-editor app test

* Remove queryinvalidator config for resource file as it is not needed

* Add backend test for navbutton syncevent
  • Loading branch information
Jondyr authored Aug 15, 2024
1 parent 1ed88b2 commit f5505d6
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 28 deletions.
8 changes: 8 additions & 0 deletions backend/src/Designer/Controllers/AppDevelopmentController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ public async Task<ActionResult> SaveFormLayout(string org, string app, [FromQuer
{
string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext);
var editingContext = AltinnRepoEditingContext.FromOrgRepoDeveloper(org, app, developer);
Dictionary<string, JsonNode> formLayouts = await _appDevelopmentService.GetFormLayouts(editingContext, layoutSetName, cancellationToken);
await _appDevelopmentService.SaveFormLayout(editingContext, layoutSetName, layoutName, formLayoutPayload.Layout, cancellationToken);

if (formLayoutPayload.ComponentIdsChange is not null && !string.IsNullOrEmpty(layoutSetName))
Expand All @@ -131,6 +132,13 @@ await _mediator.Publish(new ComponentIdChangedEvent
}, cancellationToken);
}
}
if (!formLayouts.ContainsKey(layoutName))
{
await _mediator.Publish(new LayoutPageAddedEvent
{
EditingContext = editingContext,
}, cancellationToken);
}
return Ok();
}
catch (FileNotFoundException exception)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@

using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Altinn.Studio.Designer.Events;
using Altinn.Studio.Designer.Hubs.SyncHub;
using Altinn.Studio.Designer.Infrastructure.GitRepository;
using Altinn.Studio.Designer.Models;
using Altinn.Studio.Designer.Services.Interfaces;
using MediatR;

namespace Altinn.Studio.Designer.EventHandlers.LayoutPageAdded;

public class LayoutPageAddedHandler(IAltinnGitRepositoryFactory altinnGitRepositoryFactory,
IFileSyncHandlerExecutor fileSyncHandlerExecutor) : INotificationHandler<LayoutPageAddedEvent>
{
private readonly IAltinnGitRepositoryFactory _altinnGitRepositoryFactory = altinnGitRepositoryFactory;
private readonly IFileSyncHandlerExecutor _fileSyncHandlerExecutor = fileSyncHandlerExecutor;

public async Task Handle(LayoutPageAddedEvent notification, CancellationToken cancellationToken)
{
await _fileSyncHandlerExecutor.ExecuteWithExceptionHandlingAndConditionalNotification(
notification.EditingContext,
SyncErrorCodes.LayoutPageAddSyncError,
"App/config/texts/resource.nb.json",
async () =>
{
AltinnAppGitRepository repository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(
notification.EditingContext.Org,
notification.EditingContext.Repo,
notification.EditingContext.Developer);
if (!repository.AppUsesLayoutSets())
{
return false;
}
TextResource jsonTexts = await repository.GetTextV1("nb");
int initialCount = jsonTexts.Resources.Count;
AddTextResourceIfNotExists(jsonTexts.Resources, "next", "neste");
AddTextResourceIfNotExists(jsonTexts.Resources, "back", "tilbake");
if (jsonTexts.Resources.Count != initialCount)
{
await repository.SaveTextV1("nb", jsonTexts);
return true;
}
return false;
});
}

/// <summary>
/// Adds a new TextResourceElement to the provided list if an element with the same id does not already exist.
/// </summary>
/// <param name="resources">The list of TextResourceElement to which the new element will be added.</param>
/// <param name="id">The id of the TextResourceElement to be added.</param>
/// <param name="value">The value of the TextResourceElement to be added.</param>
private static void AddTextResourceIfNotExists(List<TextResourceElement> resources, string id, string value)
{
if (!resources.Any(x => x.Id == id))
{
resources.Add(new TextResourceElement() { Id = id, Value = value });
}
}
}
9 changes: 9 additions & 0 deletions backend/src/Designer/Events/LayoutPageAddedEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Altinn.Studio.Designer.Models;
using MediatR;

namespace Altinn.Studio.Designer.Events;

public class LayoutPageAddedEvent : INotification
{
public AltinnRepoEditingContext EditingContext { get; set; }
}
1 change: 1 addition & 0 deletions backend/src/Designer/Hubs/SyncHub/SyncErrorCodes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ public static class SyncErrorCodes
public const string LayoutSetsDataTypeSyncError = nameof(LayoutSetsDataTypeSyncError);
public const string LayoutSetComponentIdSyncError = nameof(LayoutSetComponentIdSyncError);
public const string SettingsComponentIdSyncError = nameof(SettingsComponentIdSyncError);
public const string LayoutPageAddSyncError = nameof(LayoutPageAddSyncError);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
using System.Net.Http;
using System.Net.Mime;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Altinn.Studio.Designer.Models.Dto;
using Altinn.Studio.Designer.Models;
using Designer.Tests.Controllers.ApiTests;
using Designer.Tests.Utils;
using FluentAssertions;
Expand All @@ -14,12 +16,20 @@

namespace Designer.Tests.Controllers.AppDevelopmentController
{
public class SaveFormLayoutTestsBase : DesignerEndpointsTestsBase<SaveFormLayoutTestsBase>, IClassFixture<WebApplicationFactory<Program>>
public class SaveFormLayoutTestsBase(WebApplicationFactory<Program> factory)
: DesignerEndpointsTestsBase<SaveFormLayoutTestsBase>(factory), IClassFixture<WebApplicationFactory<Program>>
{

private static string VersionPrefix(string org, string repository) => $"/designer/api/{org}/{repository}/app-development";
public SaveFormLayoutTestsBase(WebApplicationFactory<Program> factory) : base(factory)

private static readonly JsonSerializerOptions s_jsonOptions = new()
{
}
WriteIndented = true,
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true
};

[Theory]
[InlineData("ttd", "app-with-layoutsets", "testUser", "testLayout", "layoutSet1", "TestData/App/ui/layoutWithUnknownProperties.json")]
Expand All @@ -42,15 +52,7 @@ public async Task SaveFormLayout_ReturnsOk(string org, string app, string develo
["componentIdsChange"] = null,
["layout"] = JsonNode.Parse(layout)
};

string jsonPayload = payload.ToJsonString();

using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, url)
{
Content = new StringContent(jsonPayload, Encoding.UTF8, MediaTypeNames.Application.Json)
};

using var response = await HttpClient.SendAsync(httpRequestMessage);
HttpResponseMessage response = await SendHttpRequest(url, payload);
response.StatusCode.Should().Be(HttpStatusCode.OK);

string relativePath = string.IsNullOrEmpty(layoutSetName)
Expand Down Expand Up @@ -81,15 +83,7 @@ public async Task SaveFormLayoutWithComponentIdsChange_ReturnsOk(string org, str
}},
["layout"] = JsonNode.Parse(layout)
};

string jsonPayload = payload.ToJsonString();

using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, url)
{
Content = new StringContent(jsonPayload, Encoding.UTF8, MediaTypeNames.Application.Json)
};

using var response = await HttpClient.SendAsync(httpRequestMessage);
HttpResponseMessage response = await SendHttpRequest(url, payload);
response.StatusCode.Should().Be(HttpStatusCode.OK);

string relativePath = string.IsNullOrEmpty(layoutSetName)
Expand All @@ -99,5 +93,44 @@ public async Task SaveFormLayoutWithComponentIdsChange_ReturnsOk(string org, str
JsonUtils.DeepEquals(layout, savedLayout).Should().BeTrue();
}

[Theory]
[InlineData("ttd", "app-with-layoutsets", "testUser", "testLayout", "layoutSet1", "TestData/App/ui/layoutWithUnknownProperties.json")]
public async Task SaveFormLayoutWithNewPageLanguageUpdate_ReturnsOk(string org, string app, string developer, string layoutName, string layoutSetName, string layoutPath)
{
string targetRepository = TestDataHelper.GenerateTestRepoName();
await CopyRepositoryForTest(org, app, developer, targetRepository);
string url = $"{VersionPrefix(org, targetRepository)}/form-layout/{layoutName}?layoutSetName={layoutSetName}";
string layout = SharedResourcesHelper.LoadTestDataAsString(layoutPath);

TestDataHelper.FileExistsInRepo(org, targetRepository, developer, "App/config/texts/resource.nb.json").Should().BeTrue();
string file = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, "App/config/texts/resource.nb.json");
TextResource textResource = JsonSerializer.Deserialize<TextResource>(file, s_jsonOptions);
textResource.Resources.Should().NotContain(x => x.Id == "next");
textResource.Resources.Should().NotContain(x => x.Id == "back");

var payload = new JsonObject
{
["componentIdsChange"] = null,
["layout"] = JsonNode.Parse(layout)
};
HttpResponseMessage response = await SendHttpRequest(url, payload);
response.StatusCode.Should().Be(HttpStatusCode.OK);

string newTextFile = TestDataHelper.GetFileFromRepo(org, targetRepository, developer, $"App/config/texts/resource.nb.json");
TextResource newTextResource = JsonSerializer.Deserialize<TextResource>(newTextFile, s_jsonOptions);
newTextResource.Resources.Should().ContainSingle(x => x.Id == "next");
newTextResource.Resources.Should().ContainSingle(x => x.Id == "back");
}

private async Task<HttpResponseMessage> SendHttpRequest(string url, JsonObject payload)
{
string jsonPayload = payload.ToJsonString();
using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, url)
{
Content = new StringContent(jsonPayload, Encoding.UTF8, MediaTypeNames.Application.Json)
};
HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage);
return response;
}
}
}
16 changes: 11 additions & 5 deletions frontend/packages/ux-editor/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { layoutSetsMock } from './testing/layoutSetsMock';
import { createQueryClientMock } from 'app-shared/mocks/queryClientMock';
import { user as userMock } from 'app-shared/mocks/mocks';
import { QueryKey } from 'app-shared/types/QueryKey';
import { PreviewContextProvider } from 'app-development/contexts/PreviewContext';

const mockQueries: Partial<ServicesContextProps> = {
getInstanceIdForPreview: jest.fn().mockImplementation(() => Promise.resolve('test')),
Expand All @@ -27,11 +28,16 @@ const renderApp = (
) => {
const queryClient = createQueryClientMock();
queryClient.setQueryData([QueryKey.CurrentUser], [userMock]);
return renderWithProviders(<App />, {
queries,
appContextProps,
queryClient,
});
return renderWithProviders(
<PreviewContextProvider>
<App />
</PreviewContextProvider>,
{
queries,
appContextProps,
queryClient,
},
);
};

describe('App', () => {
Expand Down
12 changes: 11 additions & 1 deletion frontend/packages/ux-editor/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useTextResourcesQuery } from 'app-shared/hooks/queries/useTextResources
import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams';
import { FormItemContextProvider } from './containers/FormItemContext';
import { cleanupStaleLocalStorageKeys } from './utils/localStorageUtils';
import { usePreviewContext } from 'app-development/contexts/PreviewContext';
import { FormDesignerToolbar } from '@altinn/ux-editor/containers/FormDesignerToolbar';
import { useLayoutSetsQuery } from 'app-shared/hooks/queries/useLayoutSetsQuery';

Expand Down Expand Up @@ -58,7 +59,16 @@ export function App() {
layoutSetName: selectedFormLayoutSetName,
hideDefault: true,
});
const { isSuccess: areTextResourcesFetched } = useTextResourcesQuery(org, app);
const { isSuccess: areTextResourcesFetched, data: textResources } = useTextResourcesQuery(
org,
app,
);

const { doReloadPreview } = usePreviewContext();
useEffect(() => {
doReloadPreview();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [textResources]);

const componentIsReady = areWidgetsFetched && isDataModelFetched && areTextResourcesFetched;
const componentHasError = dataModelFetchedError || widgetFetchedError;
Expand Down

0 comments on commit f5505d6

Please sign in to comment.