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

Add dataModelName parameter to GetModelMetadata endpoint #12826

Merged
merged 37 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
eed3332
update use-cases yml
nkylstad Jan 30, 2024
36554c6
Begin work
ErlingHauan May 14, 2024
9ef815e
Merge branch 'main' of https://github.com/Altinn/altinn-studio into 1…
ErlingHauan May 14, 2024
506d979
Make route 'model-metadata' take optional parameter dataModelName
ErlingHauan May 14, 2024
2db56fd
Add dataModelName to frontend path
ErlingHauan May 14, 2024
27e8d28
Add dataModelName to frontend path
ErlingHauan May 14, 2024
5803a19
Merge branch '12741-update-endpoint-fetching-datamodel' of https://gi…
ErlingHauan May 14, 2024
85d864e
Add tests for dataModelName param
ErlingHauan May 15, 2024
94d284f
Add tests for dataModelName param
ErlingHauan May 15, 2024
2989174
Include layoutSet parameter in test
ErlingHauan May 16, 2024
aa6296c
Include layoutSet parameter in test
ErlingHauan May 16, 2024
bfff87e
Update path in paths.js
ErlingHauan May 16, 2024
3a35342
Fix 404 test
ErlingHauan May 21, 2024
1882a3c
Refactor arrange part of tests into a separate method
ErlingHauan May 21, 2024
f49613f
Write test GetModelMetadata_Should_Return_Empty_Metadata_Object_When_…
ErlingHauan May 21, 2024
17c6ddf
Update code comments
ErlingHauan May 21, 2024
20b9eff
Add 'null' value for dataModelName where it is used in frontend code
ErlingHauan May 21, 2024
462e74a
Add dataModelName param to datamodelMetadataPath
ErlingHauan May 21, 2024
d6a348a
Merge branch 'main', remote-tracking branch 'origin' into 12741-updat…
ErlingHauan May 22, 2024
b058e24
Add null parameter for dataModelName for all tests
ErlingHauan May 22, 2024
088c60e
Remove redundant test case
ErlingHauan May 22, 2024
ba89e63
Merge branch 'main' into 12741-update-endpoint-fetching-datamodel
ErlingHauan May 22, 2024
4ed5842
Merge branch 'main' into 12741-update-endpoint-fetching-datamodel
ErlingHauan May 22, 2024
56c373c
Merge branch '12741-update-endpoint-fetching-datamodel' of https://gi…
ErlingHauan May 22, 2024
c6e7453
Merge branch '12741-update-endpoint-fetching-datamodel' of https://gi…
ErlingHauan May 22, 2024
d91b76a
Merge branch '12741-update-endpoint-fetching-datamodel' of https://gi…
ErlingHauan May 22, 2024
388b31b
Make dataModelNames into 'undefined' instead of 'null', and into 'tes…
ErlingHauan May 24, 2024
9b9ba70
Add 'test-data-model' where missing
ErlingHauan May 24, 2024
78cfd43
Fix merge conflicts
ErlingHauan May 24, 2024
1bbb149
Fix failing tests
ErlingHauan May 24, 2024
582399b
Add GetModelNameFromLayoutSet method and delete old code
ErlingHauan May 27, 2024
6eeac5b
Add fallback to ApplicationMetadata in GetModelName
ErlingHauan May 28, 2024
3c8e0b9
Dotnet format
ErlingHauan May 28, 2024
39cf8d3
Move layoutSetMocks into a new file
ErlingHauan May 28, 2024
11fe166
Resolve merge conflict
ErlingHauan May 28, 2024
8a9db8e
Rename layoutSetMock -> layoutSetsMock
ErlingHauan May 28, 2024
71bf2c4
Rename layoutSetMock -> layoutSetsMock
ErlingHauan May 28, 2024
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
6 changes: 4 additions & 2 deletions backend/src/Designer/Controllers/AppDevelopmentController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -271,15 +271,17 @@ public async Task<IActionResult> GetAppMetadataDataModelIds(string org, string a
/// <param name="org">Unique identifier of the organisation responsible for the app.</param>
/// <param name="app">Application identifier which is unique within an organisation.</param>
/// <param name="layoutSetName">Name of current layoutSet in ux-editor that edited layout belongs to</param>
/// <param name="dataModelName">Name of data model to fetch</param>
/// <param name="cancellationToken">An <see cref="CancellationToken"/> that observes if operation is cancelled.</param>
/// <returns>The model as JSON</returns>
[HttpGet]
[UseSystemTextJson]
[Route("model-metadata")]
public async Task<IActionResult> GetModelMetadata(string org, string app, [FromQuery] string layoutSetName, CancellationToken cancellationToken)
public async Task<IActionResult> GetModelMetadata(string org, string app, [FromQuery] string layoutSetName, [FromQuery] string dataModelName, CancellationToken cancellationToken)
{
string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext);
ModelMetadata modelMetadata = await _appDevelopmentService.GetModelMetadata(AltinnRepoEditingContext.FromOrgRepoDeveloper(org, app, developer), layoutSetName, cancellationToken);
ModelMetadata modelMetadata = await _appDevelopmentService.GetModelMetadata(AltinnRepoEditingContext.FromOrgRepoDeveloper(org, app, developer), layoutSetName, dataModelName, cancellationToken);

return Ok(modelMetadata);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@
using Altinn.Studio.Designer.Infrastructure.GitRepository;
using Altinn.Studio.Designer.Models;
using Altinn.Studio.Designer.Services.Interfaces;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Http;
using NuGet.Versioning;
using LayoutSets = Altinn.Studio.Designer.Models.LayoutSets;
using PlatformStorageModels = Altinn.Platform.Storage.Interface.Models;

namespace Altinn.Studio.Designer.Services.Implementation
{
Expand Down Expand Up @@ -190,39 +188,45 @@ public async Task<IEnumerable<string>> GetAppMetadataModelIds(AltinnRepoEditingC
}

/// <inheritdoc />
public async Task<ModelMetadata> GetModelMetadata(AltinnRepoEditingContext altinnRepoEditingContext,
string layoutSetName, CancellationToken cancellationToken = default)
public async Task<ModelMetadata> GetModelMetadata(AltinnRepoEditingContext altinnRepoEditingContext, string layoutSetName, string dataModelName, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
AltinnAppGitRepository altinnAppGitRepository =
_altinnGitRepositoryFactory.GetAltinnAppGitRepository(altinnRepoEditingContext.Org,
altinnRepoEditingContext.Repo, altinnRepoEditingContext.Developer);
ApplicationMetadata applicationMetadata =
await altinnAppGitRepository.GetApplicationMetadata(cancellationToken);
// get task_id since we might not maintain dataType ref in layout-sets-file
string taskId = await GetTaskIdBasedOnLayoutSet(altinnRepoEditingContext, layoutSetName, cancellationToken);
string modelName = GetModelName(applicationMetadata, taskId);
string modelPath;
ModelMetadata modelMetadata;

if (dataModelName is not null)
{
modelPath = $"App/models/{dataModelName}.schema.json";
modelMetadata = await _schemaModelService.GenerateModelMetadataFromJsonSchema(altinnRepoEditingContext, modelPath, cancellationToken);
return modelMetadata;
}

string modelName = await GetModelName(altinnRepoEditingContext, layoutSetName, cancellationToken);

if (string.IsNullOrEmpty(modelName))
{
return new ModelMetadata();
}
string modelPath = $"App/models/{modelName}.schema.json";
ModelMetadata modelMetadata = await _schemaModelService.GenerateModelMetadataFromJsonSchema(altinnRepoEditingContext, modelPath, cancellationToken);

modelPath = $"App/models/{modelName}.schema.json";
modelMetadata = await _schemaModelService.GenerateModelMetadataFromJsonSchema(altinnRepoEditingContext, modelPath, cancellationToken);
return modelMetadata;
}

private string GetModelName(ApplicationMetadata applicationMetadata, [CanBeNull] string taskId)
private async Task<string> GetModelName(AltinnRepoEditingContext altinnRepoEditingContext, string layoutSetName, CancellationToken cancellationToken = default)
{
// fallback to first model if no task_id is provided (no layout sets)
if (taskId == null)
if (string.IsNullOrEmpty(layoutSetName))
{
// Fallback to first model in app metadata if no layout set is provided
AltinnAppGitRepository altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(altinnRepoEditingContext.Org, altinnRepoEditingContext.Repo, altinnRepoEditingContext.Developer);
ApplicationMetadata applicationMetadata = await altinnAppGitRepository.GetApplicationMetadata(cancellationToken);
return applicationMetadata.DataTypes.FirstOrDefault(data => data.AppLogic != null && !string.IsNullOrEmpty(data.AppLogic.ClassRef) && !string.IsNullOrEmpty(data.TaskId))?.Id ?? string.Empty;
}

PlatformStorageModels.DataType data = applicationMetadata.DataTypes
.FirstOrDefault(data => data.AppLogic != null && DoesDataTaskMatchTaskId(data, taskId) && !string.IsNullOrEmpty(data.AppLogic.ClassRef));
LayoutSets layoutSets = await GetLayoutSets(altinnRepoEditingContext, cancellationToken);
var foundLayoutSet = layoutSets.Sets.Find(set => set.Id == layoutSetName);

return data?.Id ?? string.Empty;
return foundLayoutSet.DataType;
}

private IEnumerable<string> GetAppMetadataModelIds(ApplicationMetadata applicationMetadata, bool onlyUnReferenced)
Expand All @@ -240,23 +244,6 @@ private IEnumerable<string> GetAppMetadataModelIds(ApplicationMetadata applicati
return appMetaDataDataTypes.Select(datatype => datatype.Id);
}

private bool DoesDataTaskMatchTaskId(PlatformStorageModels.DataType data, [CanBeNull] string taskId)
{
return string.IsNullOrEmpty(taskId) || data.TaskId == taskId;
}

private async Task<string> GetTaskIdBasedOnLayoutSet(AltinnRepoEditingContext altinnRepoEditingContext, string layoutSetName, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(layoutSetName))
{
// App without layout sets --> no need for task_id, we just retrieve the first occurence of a dataType with a classRef
return null;
}
LayoutSets layoutSets = await GetLayoutSets(altinnRepoEditingContext, cancellationToken);

return layoutSets?.Sets?.Find(set => set.Id == layoutSetName)?.Tasks[0];
}

/// <inheritdoc />
public async Task<LayoutSets> GetLayoutSets(AltinnRepoEditingContext altinnRepoEditingContext,
CancellationToken cancellationToken = default)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,11 @@ public Task<IEnumerable<string>> GetAppMetadataModelIds(
/// </summary>
/// <param name="altinnRepoEditingContext">An <see cref="AltinnRepoEditingContext"/>.</param>
/// <param name="layoutSetName">Name of layoutSet to fetch corresponding model metadata for</param>
/// <param name="dataModelName">Name of data model to fetch</param>
/// <param name="cancellationToken">An <see cref="CancellationToken"/> that observes if operation is cancelled.</param>
/// <returns>The model metadata for a given layout set.</returns>
public Task<ModelMetadata> GetModelMetadata(
AltinnRepoEditingContext altinnRepoEditingContext, [CanBeNull] string layoutSetName,
AltinnRepoEditingContext altinnRepoEditingContext, [CanBeNull] string layoutSetName, [CanBeNull] string dataModelName,
CancellationToken cancellationToken = default);

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,39 +20,57 @@ public GetModelMetadataTests(WebApplicationFactory<Program> factory) : base(fact
}

[Theory]
[InlineData("ttd", "app-with-layoutsets", "testUser", "layoutSet1", "TestData/Model/Metadata/datamodel.json")]
[InlineData("ttd", "app-without-layoutsets", "testUser", null, "TestData/Model/Metadata/datamodel.json")]
public async Task GetModelMetadata_Should_Return_ModelMetadata(string org, string app, string developer, string layoutSetName, string expectedModelMetadataPath)
[InlineData("ttd", "app-with-layoutsets", "testUser", "layoutSet1", null)]
[InlineData("ttd", "app-without-layoutsets", "testUser", null, null)]
public async Task GetModelMetadata_Should_Return_ModelMetadata_Based_On_LayoutSet_When_DataModelName_Is_Undefined(string org, string app, string developer, string layoutSetName, string dataModelName)
{
string targetRepository = TestDataHelper.GenerateTestRepoName();
await CopyRepositoryForTest(org, app, developer, targetRepository);

string expectedModelMetadata = await AddModelMetadataToRepo(TestRepoPath, expectedModelMetadataPath);

string url = $"{VersionPrefix(org, targetRepository)}/model-metadata?layoutSetName={layoutSetName}";
// Arrange
(string url, string expectedModelMetadata) = await ArrangeGetModelMetadataTest(org, app, developer, layoutSetName, dataModelName);
using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url);

// Act
using var response = await HttpClient.SendAsync(httpRequestMessage);
string responseContent = await response.Content.ReadAsStringAsync();

// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
responseContent.Should().Be(expectedModelMetadata);
JsonUtils.DeepEquals(expectedModelMetadata, responseContent).Should().BeTrue();
}

[Theory]
[InlineData("ttd", "app-with-layoutsets", "testUser", "layoutSet1", "datamodel")]
[InlineData("ttd", "app-without-layoutsets", "testUser", null, "datamodel")]
public async Task GetModelMetadata_Should_Return_ModelMetadata_When_DataModelName_Is_Specified(string org, string app, string developer, string layoutSetName, string dataModelName)
{
// Arrange
(string url, string expectedModelMetadata) = await ArrangeGetModelMetadataTest(org, app, developer, layoutSetName, dataModelName);
using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url);

// Act
using var response = await HttpClient.SendAsync(httpRequestMessage);
string responseContent = await response.Content.ReadAsStringAsync();

// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
responseContent.Should().Be(expectedModelMetadata);
JsonUtils.DeepEquals(expectedModelMetadata, responseContent).Should().BeTrue();
}

[Theory]
[InlineData("ttd", "app-with-layoutsets", "testUser", "layoutSet3")]
[InlineData("ttd", "app-without-layoutsets-mismatch-modelname", "testUser", null)]
public async Task GetModelMetadata_Should_Return_404_When_No_Corresponding_Datamodel_Exists(string org, string app, string developer, string layoutSetName)
[InlineData("ttd", "app-with-layoutsets", "testUser", "layoutSet3", null)]
[InlineData("ttd", "app-without-layoutsets-mismatch-modelname", "testUser", null, null)]
[InlineData("ttd", "app-with-layoutsets", "testUser", null, "non-existing-dataModelName")]
standeren marked this conversation as resolved.
Show resolved Hide resolved
public async Task GetModelMetadata_Should_Return_404_When_No_Corresponding_Datamodel_Exists(string org, string app, string developer, string layoutSetName, string dataModelName)
{
string targetRepository = TestDataHelper.GenerateTestRepoName();
await CopyRepositoryForTest(org, app, developer, targetRepository);

string url = $"{VersionPrefix(org, targetRepository)}/model-metadata?layoutSetName={layoutSetName}";
// Arrange
(string url, _) = await ArrangeGetModelMetadataTest(org, app, developer, layoutSetName, dataModelName);
using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url);

// Act
using var response = await HttpClient.SendAsync(httpRequestMessage);

// Assert
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
}

Expand All @@ -63,5 +81,18 @@ private async Task<string> AddModelMetadataToRepo(string createdFolderPath, stri
await File.WriteAllTextAsync(filePath, modelMetadata);
return modelMetadata;
}

private async Task<(string url, string expectedModelMetadata)> ArrangeGetModelMetadataTest(string org, string app, string developer, string layoutSetName, string dataModelName)
{
string targetRepository = TestDataHelper.GenerateTestRepoName();
await CopyRepositoryForTest(org, app, developer, targetRepository);

const string expectedModelMetadataPath = "TestData/Model/Metadata/datamodel.json";
string expectedModelMetadata = await AddModelMetadataToRepo(TestRepoPath, expectedModelMetadataPath);

string url = $"{VersionPrefix(org, targetRepository)}/model-metadata?layoutSetName={layoutSetName}&dataModelName={dataModelName}";

return (url, expectedModelMetadata);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { queryClientMock } from 'app-shared/mocks/queryClientMock';
import { renderWithProviders } from '@altinn/ux-editor/testing/mocks';
import { layoutSet1NameMock, layoutSetsMock } from '@altinn/ux-editor/testing/layoutMock';
import { layoutSet1NameMock, layoutSetsMock } from '@altinn/ux-editor/testing/layoutSetsMock';
import type { AppPreviewSubMenuProps } from './AppPreviewSubMenu';
import { AppPreviewSubMenu } from './AppPreviewSubMenu';
import type { LayoutSets } from 'app-shared/types/api/LayoutSetsResponse';
Expand Down
2 changes: 1 addition & 1 deletion frontend/packages/shared/src/api/paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const widgetSettingsPath = (org, app) => `${basePath}/${org}/${app}/app-d
export const optionListIdsPath = (org, app) => `${basePath}/${org}/${app}/app-development/option-list-ids`; // Get
export const ruleConfigPath = (org, app, layoutSetName) => `${basePath}/${org}/${app}/app-development/rule-config?${s({ layoutSetName })}`; // Get, Post
export const appMetadataModelIdsPath = (org, app, onlyUnReferenced) => `${basePath}/${org}/${app}/app-development/model-ids?${s({ onlyUnReferenced })}`; // Get
export const datamodelMetadataPath = (org, app, layoutSetName) => `${basePath}/${org}/${app}/app-development/model-metadata?${s({ layoutSetName })}`; // Get
export const datamodelMetadataPath = (org, app, layoutSetName, dataModelName) => `${basePath}/${org}/${app}/app-development/model-metadata?${s({ layoutSetName })}&${s({ dataModelName })}`; // Get
export const layoutNamesPath = (org, app) => `${basePath}/${org}/${app}/app-development/layout-names`; // Get
export const layoutSetsPath = (org, app) => `${basePath}/${org}/${app}/app-development/layout-sets`; // Get
export const layoutSetPath = (org, app, layoutSetIdToUpdate) => `${basePath}/${org}/${app}/app-development/layout-set/${layoutSetIdToUpdate}`; // Put, Delete
Expand Down
2 changes: 1 addition & 1 deletion frontend/packages/shared/src/api/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export const getAppReleases = (owner: string, app: string) => get<AppReleasesRes
export const getAppVersion = (org: string, app: string) => get<AppVersion>(appVersionPath(org, app));
export const getBranchStatus = (owner: string, app: string, branch: string) => get<BranchStatus>(branchStatusPath(owner, app, branch));
export const getDatamodel = (owner: string, app: string, modelPath: string) => get<JsonSchema>(datamodelPath(owner, app, modelPath));
export const getDatamodelMetadata = (owner: string, app: string, layoutSetName: string) => get<DatamodelMetadataResponse>(datamodelMetadataPath(owner, app, layoutSetName));
export const getDatamodelMetadata = (owner: string, app: string, layoutSetName: string, dataModelName: string) => get<DatamodelMetadataResponse>(datamodelMetadataPath(owner, app, layoutSetName, dataModelName));
export const getDatamodelsJson = (owner: string, app: string) => get<DatamodelMetadataJson[]>(datamodelsPath(owner, app));
export const getDatamodelsXsd = (owner: string, app: string) => get<DatamodelMetadataXsd[]>(datamodelsXsdPath(owner, app));
export const getDeployPermissions = (owner: string, app: string) => get<string[]>(deployPermissionsPath(owner, app));
Expand Down
2 changes: 1 addition & 1 deletion frontend/packages/ux-editor-v3/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext';
import { appStateMock } from './testing/stateMocks';
import type { AppContextProps } from './AppContext';
import ruleHandlerMock from './testing/ruleHandlerMock';
import { layoutSetsMock } from './testing/layoutMock';
import { layoutSetsMock } from './testing/layoutSetsMock';

const { selectedLayoutSet } = appStateMock.formDesigner.layout;

Expand Down
2 changes: 1 addition & 1 deletion frontend/packages/ux-editor-v3/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function App() {
const { data: layoutSets, isSuccess: areLayoutSetsFetched } = useLayoutSetsQuery(org, app);
const { isSuccess: areWidgetsFetched, isError: widgetFetchedError } = useWidgetsQuery(org, app);
const { isSuccess: isDatamodelFetched, isError: dataModelFetchedError } =
useDatamodelMetadataQuery(org, app, selectedLayoutSet);
useDatamodelMetadataQuery(org, app, selectedLayoutSet, undefined);
const { isSuccess: areTextResourcesFetched } = useTextResourcesQuery(org, app);

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import userEvent from '@testing-library/user-event';
import { LayoutSetsContainer } from './LayoutSetsContainer';
import { queryClientMock } from 'app-shared/mocks/queryClientMock';
import { renderWithMockStore } from '../../testing/mocks';
import { layoutSet1NameMock, layoutSet2NameMock, layoutSetsMock } from '../../testing/layoutMock';
import {
layoutSet1NameMock,
layoutSet2NameMock,
layoutSetsMock,
} from '@altinn/ux-editor-v3/testing/layoutSetsMock';
import type { AppContextProps } from '../../AppContext';
import { appStateMock } from '../../testing/stateMocks';
import { QueryKey } from 'app-shared/types/QueryKey';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { mockUseTranslation } from '@studio/testing/mocks/i18nMock';
import { ComponentTypeV3 } from 'app-shared/types/ComponentTypeV3';
import { useDatamodelMetadataQuery } from '../../hooks/queries/useDatamodelMetadataQuery';
import type { DatamodelMetadataResponse } from 'app-shared/types/api';
import { layoutSet1NameMock } from '@altinn/ux-editor-v3/testing/layoutMock';
import { dataModelNameMock, layoutSet1NameMock } from '@altinn/ux-editor-v3/testing/layoutSetsMock';
import { app, org } from '@studio/testing/testids';

const user = userEvent.setup();
Expand Down Expand Up @@ -226,7 +226,8 @@ const waitForData = async () => {
const dataModelMetadataResult = renderHookWithMockStore(
{},
{ getDatamodelMetadata },
)(() => useDatamodelMetadataQuery(org, app, layoutSet1NameMock)).renderHookResult.result;
)(() => useDatamodelMetadataQuery(org, app, layoutSet1NameMock, dataModelNameMock))
.renderHookResult.result;
await waitFor(() => expect(dataModelMetadataResult.current.isSuccess).toBe(true));
await waitFor(() => expect(layoutSchemaResult.current[0].isSuccess).toBe(true));
};
Expand Down
Loading
Loading