Skip to content

Commit

Permalink
feat: support collections of primitives as data source parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
ascott18 committed Jul 18, 2024
1 parent 655da89 commit 60b64c8
Show file tree
Hide file tree
Showing 13 changed files with 86 additions and 29 deletions.
2 changes: 1 addition & 1 deletion docs/modeling/model-components/data-sources.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ All methods on `IDataSource<T>` take a parameter that contains all the client-sp

## Custom Parameters

On any data source that you create, you may add additional properties annotated with `[Coalesce]` that will then be exposed as parameters to the client. These property parameters are currently restricted to primitives (numeric types, strings) and dates (DateTime, DateTimeOffset). Property parameter primitives may be expanded to allow for more types in the future.
On any data source that you create, you may add additional properties annotated with `[Coalesce]` that will then be exposed as parameters to the client. These property parameters can be primitives (numeric types, strings, enums), dates (DateTime, DateTimeOffset, DateOnly, TimeOnly), and collections of the preceding types.

``` c#
[Coalesce]
Expand Down
5 changes: 4 additions & 1 deletion playground/Coalesce.Domain/Person.cs
Original file line number Diff line number Diff line change
Expand Up @@ -366,11 +366,14 @@ public class NamesStartingWithAWithCases : StandardDataSource<Person, AppDbConte
{
public NamesStartingWithAWithCases(CrudContext<AppDbContext> context) : base(context) { }

Check warning on line 367 in playground/Coalesce.Domain/Person.cs

View workflow job for this annotation

GitHub Actions / build / build-dotnet

Non-nullable property 'AllowedStatuses' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 367 in playground/Coalesce.Domain/Person.cs

View workflow job for this annotation

GitHub Actions / build / build-dotnet

Non-nullable property 'AllowedStatuses' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 367 in playground/Coalesce.Domain/Person.cs

View workflow job for this annotation

GitHub Actions / build / build-dotnet

Non-nullable property 'AllowedStatuses' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 367 in playground/Coalesce.Domain/Person.cs

View workflow job for this annotation

GitHub Actions / build / build-dotnet

Non-nullable property 'AllowedStatuses' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 367 in playground/Coalesce.Domain/Person.cs

View workflow job for this annotation

GitHub Actions / build / validate-ko-playground

Non-nullable property 'AllowedStatuses' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 367 in playground/Coalesce.Domain/Person.cs

View workflow job for this annotation

GitHub Actions / build / validate-ko-playground

Non-nullable property 'AllowedStatuses' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 367 in playground/Coalesce.Domain/Person.cs

View workflow job for this annotation

GitHub Actions / build / validate-vue2-playground

Non-nullable property 'AllowedStatuses' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 367 in playground/Coalesce.Domain/Person.cs

View workflow job for this annotation

GitHub Actions / build / validate-vue2-playground

Non-nullable property 'AllowedStatuses' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 367 in playground/Coalesce.Domain/Person.cs

View workflow job for this annotation

GitHub Actions / build / validate-vue3-playground

Non-nullable property 'AllowedStatuses' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 367 in playground/Coalesce.Domain/Person.cs

View workflow job for this annotation

GitHub Actions / build / validate-vue3-playground

Non-nullable property 'AllowedStatuses' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

[Coalesce]
public List<Case.Statuses> AllowedStatuses { get; set; }

public override IQueryable<Person> GetQuery(IDataSourceParameters parameters)
{
Db.Cases
.Include(c => c.CaseProducts).ThenInclude(cp => cp.Product)
.Where(c => c.Status == Case.Statuses.Open || c.Status == Case.Statuses.InProgress)
.Where(c => AllowedStatuses.Contains(c.Status))
.Load();

return Db.People
Expand Down
7 changes: 6 additions & 1 deletion playground/Coalesce.Web.Vue3/src/components/test.vue
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ import {
PersonListViewModel,
} from "../viewmodels.g";
import { CaseApiClient, PersonApiClient } from "../api-clients.g";
import { Person } from "../models.g";
import { Person, Statuses } from "../models.g";
@Component({
components: {},
Expand All @@ -101,7 +101,12 @@ export default class Test extends Base {
caseVm = new CaseViewModel();
async created() {
this.personList.$dataSource =
new Person.DataSources.NamesStartingWithAWithCases({
allowedStatuses: [Statuses.Open, Statuses.InProgress],
});
this.personList.$params.noCount = true;
this.personList.$load();
await this.caseVm.$load(15);
await this.caseVm.downloadImage();
Expand Down
13 changes: 13 additions & 0 deletions playground/Coalesce.Web.Vue3/src/metadata.g.ts

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

5 changes: 5 additions & 0 deletions playground/Coalesce.Web.Vue3/src/models.g.ts

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

Original file line number Diff line number Diff line change
Expand Up @@ -605,27 +605,6 @@ private void WriteDataSourcesMetadata(TypeScriptCodeBuilder b, ClassViewModel mo
{
WriteDataSourceMetadata(b, model, source);
}

// Not sure we need to explicitly declare the default source.
// We can just use the absense of a data source to represent the default.
/*
var defaultSource = dataSources.SingleOrDefault(s => s.IsDefaultDataSource);
if (defaultSource != null)
{
var name = defaultSource.ClientTypeName.ToCamelCase();
b.Line($"get default() {{ return this.{name} }},");
}
else
{
using (b.Block($"default:", ','))
{
b.StringProp("type", "dataSource");
b.StringProp("name", "default");
b.StringProp("displayName", "Default");
b.Line("params: {}");
}
}
*/
}
}

Expand All @@ -634,8 +613,6 @@ private void WriteDataSourcesMetadata(TypeScriptCodeBuilder b, ClassViewModel mo
/// </summary>
private void WriteDataSourceMetadata(TypeScriptCodeBuilder b, ClassViewModel model, ClassViewModel source)
{
// TODO: Should we be camel-casing the names of data sources in the metadata?
// TODO: OR, should we be not camel casing the members we place on the domain[key: string] objects?
using (b.Block($"{source.ClientTypeName.ToCamelCase()}:", ','))
{
b.StringProp("type", "dataSource");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,11 +225,14 @@ public class NamesStartingWithAWithCases : StandardDataSource<Person, AppDbConte
{
public NamesStartingWithAWithCases(CrudContext<AppDbContext> context) : base(context) { }

[Coalesce]
public List<Case.Statuses> AllowedStatuses { get; set; }

public override IQueryable<Person> GetQuery(IDataSourceParameters parameters)
{
Db.Cases
.Include(c => c.CaseProducts).ThenInclude(cp => cp.Product)
.Where(c => c.Status == Case.Statuses.Open || c.Status == Case.Statuses.InProgress)
.Where(c => AllowedStatuses.Contains(c.Status))
.Load();

return Db.People
Expand Down
2 changes: 1 addition & 1 deletion src/IntelliTect.Coalesce/TypeDefinition/ClassViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ internal IReadOnlyCollection<PropertyViewModel> Properties
.Where(p =>
!p.IsInternalUse && p.HasPublicSetter && p.HasAttribute<CoalesceAttribute>()
// These are the only supported types, for now
&& (p.Type.IsPrimitive || p.Type.IsDateOrTime)
&& (p.PureType.IsPrimitive || p.PureType.IsDateOrTime)
);

/// <summary>
Expand Down
1 change: 1 addition & 0 deletions src/coalesce-vue/src/api-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,7 @@ export type ApiResultPromise<T> = Promise<
/** Axios instance to be used by all Coalesce API requests. Can be configured as needed. */
export const AxiosClient = axios.create();
AxiosClient.defaults.baseURL = "/api";
AxiosClient.defaults.paramsSerializer = objectToQueryString;

// Set X-Requested-With: XmlHttpRequest to prevent aspnetcore from serving HTML and redirects to API requests.
// https://github.com/dotnet/aspnetcore/blob/c440ebcf49badd49f0e2cdde1b0a74992af04158/src/Security/Authentication/Cookies/src/CookieAuthenticationEvents.cs#L107-L111
Expand Down
8 changes: 7 additions & 1 deletion src/coalesce-vue/src/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,13 @@ export interface DataSourceType extends Metadata {
* Stored as `props` so it can be treated like a ModelType/ObjectType in many cases.
*/
readonly props: {
[paramName in string]: PrimitiveProperty | DateProperty | EnumProperty;
[paramName in string]:
| PrimitiveProperty
| DateProperty
| EnumProperty
| (BasicCollectionProperty & {
itemType: PrimitiveValue | DateValue | EnumValue;
});
};
// NOTE: this union is the currently supported set of data source parameters.
// When we support more types in the future (e.g. objects), adjust accordingly.
Expand Down
26 changes: 26 additions & 0 deletions src/coalesce-vue/test/api-client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { Student as StudentMeta } from "./targets.metadata";
import { Student, Advisor } from "./targets.models";

import { ComplexModelApiClient } from "../../test-targets/api-clients.g";
import { PersonListViewModel } from "@test-targets/viewmodels.g";
import { Person, Statuses } from "@test-targets/models.g";

function makeAdapterMock(result?: any) {
return makeEndpointMock<AxiosRequestConfig>(result);
Expand Down Expand Up @@ -361,6 +363,30 @@ describe("$invoke", () => {

expect(mock.mock.calls[0][0].data).toBe("name=bob&studentAdvisorId=");
});

test("data source collection parameter", async () => {
const mock = mockEndpoint(
"/Person/list",
vitest.fn((req: AxiosRequestConfig) => {
return {
wasSuccessful: true,
list: [],
};
})
);

const personList = new PersonListViewModel();
personList.$dataSource = new Person.DataSources.NamesStartingWithAWithCases(
{
allowedStatuses: [Statuses.Open, Statuses.InProgress],
}
);
await personList.$load();

expect(AxiosClient.getUri(mock.mock.lastCall![0])).toBe(
"/api/Person/list?page=1&pageSize=10&dataSource=NamesStartingWithAWithCases&dataSource.allowedStatuses=0&dataSource.allowedStatuses=1"
);
});
});

describe("$makeCaller", () => {
Expand Down
13 changes: 13 additions & 0 deletions src/test-targets/metadata.g.ts

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

5 changes: 5 additions & 0 deletions src/test-targets/models.g.ts

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

0 comments on commit 60b64c8

Please sign in to comment.