Skip to content

Commit

Permalink
Merge branch 'fix/rest-api-includedinpage' into release/4.1
Browse files Browse the repository at this point in the history
  • Loading branch information
tomasherceg committed Jan 17, 2023
2 parents 1a8edbc + 2c95ece commit 44c9178
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 17 deletions.
3 changes: 0 additions & 3 deletions .github/setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,6 @@ runs:
3.1.x
- if: ${{ runner.os == 'Windows' }}
uses: microsoft/[email protected]
- if: ${{ runner.os == 'Windows' }}
run: choco install dotnetcore-3.1-windowshosting -y
shell: pwsh

# restore packages
- if: ${{ runner.os == 'Windows' }}
Expand Down
7 changes: 7 additions & 0 deletions .github/uitest/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ runs:
--config "${{ inputs.build-configuration }}"
--environment "${{ inputs.runtime-environment }}"
shell: bash

- if: ${{ runner.os == 'Windows' }}
run: choco install dotnet-aspnetcoremodule-v2 -y
shell: pwsh
- if: ${{ runner.os == 'Windows' }}
run: iisreset
shell: pwsh
- if: ${{ runner.os == 'Windows' }}
name: uitest.ps1
run: .\.github\uitest\uitest.ps1
Expand Down
15 changes: 15 additions & 0 deletions .github/uitest/uitest.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,21 @@ function Test-Sample {
Sleep 5
}
}

# print out all log files
Export-IISConfiguration -PhysicalPath c:\inetpub -DontExportKeys -Force
foreach ($log in dir c:\inetpub\*.config) {
write-host $log
get-content $log | write-host
}
foreach ($log in dir c:\inetpub\logs\logfiles\*\*.log) {
write-host $log
get-content $log | write-host
}
foreach ($log in dir $root\artifacts\**\*.log) {
write-host $log
get-content $log | write-host
}
throw "The sample '${sampleName}' failed to start."
}
}
Expand Down
45 changes: 32 additions & 13 deletions src/Framework/Framework/Resources/Scripts/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,39 @@ type ApiComputed<T> =
refreshValue: () => PromiseLike<any>
};

type CachedValue = {
class CachedValue {
_stateManager?: StateManager<any>
_isLoading?: boolean
_promise?: PromiseLike<any>
_referenceCount: number
_elements: HTMLElement[] = []

constructor(public _cache: Cache) {
}

registerElement(element: HTMLElement, sharingKeyValue: string) {
if (!this._elements.includes(element)) {
this._elements.push(element);

ko.utils.domNodeDisposal.addDisposeCallback(element, () => {
this.unregisterElement(element, sharingKeyValue);
});
}
}

unregisterElement(element: HTMLElement, sharingKeyValue: string) {
const oldElementIndex = this._elements.indexOf(element);
if (oldElementIndex >= 0) {
this._elements.splice(oldElementIndex, 1);
}
if (!this._elements.length) {
delete this._cache[sharingKeyValue];
}
}
}

type Cache = { [k: string]: CachedValue }

const cachedValues: {
[key: string]: KnockoutObservable<any>
} = {};
const cachedValues: Cache = {};

export function invoke<T>(
target: any,
Expand All @@ -33,7 +54,7 @@ export function invoke<T>(
sharingKeyProvider: (args: any[]) => string[],
lifetimeElement: HTMLElement
): ApiComputed<T> {
const cache: Cache = cacheElement ? ((<any> cacheElement)["apiCachedValues"] ??= {}) : cachedValues;
const cache: Cache = cacheElement ? ((<any>cacheElement)["apiCachedValues"] ??= {}) : cachedValues;
const $type: TypeDefinition = { type: "dynamic" }

let args: any[];
Expand All @@ -48,17 +69,14 @@ export function invoke<T>(
let oldKey = sharingKeyValue
sharingKeyValue = methodName + ":" + sharingKeyProvider(args)
const oldCached = cachedValue
cachedValue = cache[sharingKeyValue] ??= {_referenceCount: 0}
cachedValue = cache[sharingKeyValue] ??= new CachedValue(cache)
if (cachedValue === oldCached) {
return
}
cachedValue._referenceCount++

cachedValue.registerElement(lifetimeElement, sharingKeyValue);
if (oldCached) {
oldCached._referenceCount--
if (oldCached._referenceCount <= 0) {
delete cache[oldKey]
}
oldCached.unregisterElement(lifetimeElement, oldKey);
}

if (cachedValue._stateManager == null)
Expand All @@ -69,6 +87,7 @@ export function invoke<T>(
}
stateManager(cachedValue._stateManager)
}

function reloadApi(): PromiseLike<any> {
if (!cachedValue._isLoading) {
cachedValue._isLoading = true
Expand Down
2 changes: 1 addition & 1 deletion src/Samples/Api.AspNetCore/web.config
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="dotnet" arguments=".\DotVVM.Samples.BasicSamples.Api.AspNetCore.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess">
<aspNetCore processPath="dotnet" arguments=".\DotVVM.Samples.BasicSamples.Api.AspNetCore.dll" stdoutLogEnabled="true" stdoutLogFile=".\logs\stdout" hostingModel="inprocess">
<environmentVariables>
<environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Development" />
</environmentVariables>
Expand Down
1 change: 1 addition & 0 deletions src/Samples/Common/DotVVM.Samples.Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
<None Remove="Views\FeatureSamples\Api\ApiInSpa_PageB.dothtml" />
<None Remove="Views\FeatureSamples\Api\ApiRefresh.dothtml" />
<None Remove="Views\FeatureSamples\Api\CollectionOddEvenWithRestApi.dothtml" />
<None Remove="Views\FeatureSamples\Api\IncludedInPage.dothtml" />
<None Remove="Views\FeatureSamples\Attribute\ToStringConversion.dothtml" />
<None Remove="Views\FeatureSamples\AutoUI\AutoEditor.dothtml" />
<None Remove="Views\FeatureSamples\AutoUI\AutoForm.dothtml" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DotVVM.Framework.ViewModel;

namespace DotVVM.Samples.Common.ViewModels.FeatureSamples.Api
{
public class IncludedInPageViewModel : DotvvmViewModelBase
{
public bool Visible { get; set; }

public int RefreshCounter { get; set; }
}
}

51 changes: 51 additions & 0 deletions src/Samples/Common/Views/FeatureSamples/Api/IncludedInPage.dothtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
@viewModel DotVVM.Samples.Common.ViewModels.FeatureSamples.Api.IncludedInPageViewModel, DotVVM.Samples.Common

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>

<h1>API and IncludeInPage</h1>

<dot:Button Text="Refresh data" Click="{staticCommand: _root.RefreshCounter = _root.RefreshCounter + 1}" data-ui="refresh-counter" />

<dot:Button Text="Open dialog static command" Click="{staticCommand: _root.Visible = true}" data-ui="open-static-command" />
<dot:Button Text="Open dialog command" Click="{command: _root.Visible = true}" data-ui="open-command" />

<dialog IncludeInPage="{value: _root.Visible}" open="{value: _root.Visible}">

<dot:GridView DataSource="{value: _api.RefreshOnChange(_apiCore.GetOrdersAll(11), _root.RefreshCounter)}"
data-ui="grid">
<dot:GridViewTemplateColumn>{{value: Number}}</dot:GridViewTemplateColumn>
</dot:GridView>

<dot:Button Text="Close dialog static command" Click="{staticCommand: _root.Visible = false}" data-ui="close-static-command" />
<dot:Button Text="Close dialog command" Click="{command: _root.Visible = false}" data-ui="close-command" />

</dialog>

<h3>Request log</h3>
<ol ID="request-log" ClientIDMode="Static">
</ol>

<script type="text/javascript">
var fetchBackup = window.fetch;
window.fetch = function (url, init) {
if (typeof url === "string" && url.indexOf("/api/Orders") >= 0) {
var ol = document.getElementById("request-log");
var li = document.createElement("li");
li.innerText = init.method + " " + url.substring(url.indexOf("/api/Orders"));
ol.appendChild(li);
}
return fetchBackup.apply(window, arguments);
};
</script>

</body>
</html>


Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

96 changes: 96 additions & 0 deletions src/Samples/Tests/Tests/Feature/ApiTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,102 @@ public void Feature_Api_CollectionOddEvenWithRestApi()
});
}

[Fact]
public void Feature_Api_IncludedInPage()
{
RunInAllBrowsers(browser => {
browser.NavigateToUrl(SamplesRouteUrls.FeatureSamples_Api_IncludedInPage);

// ensure there are no requests on page load - the dialog is hidden
browser.Wait(5000);
CheckRequests();

// open the dialog
browser.Single("open-static-command", SelectByDataUi).Click();

// check that the list of orders is loaded
CheckRequests(
"GET /api/Orders?companyId=11"
);
var grid = browser.Single("table");
grid.FindElements("tr").ThrowIfDifferentCountThan(11);

// close the dialog - no additional request should appear
browser.Single("close-static-command", SelectByDataUi).Click();
CheckRequests(
"GET /api/Orders?companyId=11"
);

// open the dialog again - the view should be refreshed
browser.Single("open-static-command", SelectByDataUi).Click();
CheckRequests(
"GET /api/Orders?companyId=11",
"GET /api/Orders?companyId=11"
);

// close the dialog - no additional request should appear
browser.Single("close-static-command", SelectByDataUi).Click();
CheckRequests(
"GET /api/Orders?companyId=11",
"GET /api/Orders?companyId=11"
);

// do the same thing with commands
browser.Single("open-command", SelectByDataUi).Click();
CheckRequests(
"GET /api/Orders?companyId=11",
"GET /api/Orders?companyId=11",
"GET /api/Orders?companyId=11"
);

// close the dialog - no additional request should appear
browser.Single("close-command", SelectByDataUi).Click();
CheckRequests(
"GET /api/Orders?companyId=11",
"GET /api/Orders?companyId=11",
"GET /api/Orders?companyId=11"
);

// click the refresh counter - nothing should happen since the dialog is not visible
browser.Single("refresh-counter", SelectByDataUi).Click();
CheckRequests(
"GET /api/Orders?companyId=11",
"GET /api/Orders?companyId=11",
"GET /api/Orders?companyId=11"
);

// open the dialog again - the view should be refreshed
browser.Single("open-static-command", SelectByDataUi).Click();
CheckRequests(
"GET /api/Orders?companyId=11",
"GET /api/Orders?companyId=11",
"GET /api/Orders?companyId=11",
"GET /api/Orders?companyId=11"
);

// click the refresh counter - now it should refresh data because the dialog is visible
browser.Single("refresh-counter", SelectByDataUi).Click();
CheckRequests(
"GET /api/Orders?companyId=11",
"GET /api/Orders?companyId=11",
"GET /api/Orders?companyId=11",
"GET /api/Orders?companyId=11",
"GET /api/Orders?companyId=11"
);

void CheckRequests(params string[] expected)
{
var items = browser.FindElements("#request-log li");
items.ThrowIfDifferentCountThan(expected.Length);

for (var i = 0; i < expected.Length; i++)
{
AssertUI.TextEquals(items[i], expected[i]);
}
}
});
}

public ApiTests(ITestOutputHelper output) : base(output)
{
}
Expand Down

0 comments on commit 44c9178

Please sign in to comment.