Skip to content

Commit

Permalink
feat: Add process and precedingProcess to dialog as optional fields (#…
Browse files Browse the repository at this point in the history
…1092)

## Description
Added process and precedingProcess to dialog with validation to make
sure its a valid URI

## Related Issue(s)
[#565](#565)
## Verification

- [x] **Your** code builds clean without any errors or warnings
- [x] Manual testing done (required)
- [x] Relevant automated test added (if you find this hard, leave it and
we'll help out)

## Documentation

- [ ] Documentation is updated (either in `docs`-directory, Altinnpedia
or a separate linked PR in
[altinn-studio-docs.](https://github.com/Altinn/altinn-studio-docs), if
applicable)

---------

Co-authored-by: Amund Myrbostad <amund.myrbostad@digdir.no>
Co-authored-by: Ole Jørgen Skogstad <skogstad@softis.net>
Co-authored-by: Magnus Sandgren <5285192+MagnusSandgren@users.noreply.github.com>
  • Loading branch information
4 people authored Sep 11, 2024
1 parent 90e5cec commit 2bf0d30
Show file tree
Hide file tree
Showing 39 changed files with 2,174 additions and 46 deletions.
4 changes: 4 additions & 0 deletions docs/schema/V1/schema.verified.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ type Dialog {
serviceResourceType: String!
party: String!
progress: Int
process: String
precedingProcess: String
extendedStatus: String
externalReference: String
visibleFrom: DateTime
Expand Down Expand Up @@ -172,6 +174,8 @@ type SearchDialog {
serviceResourceType: String!
party: String!
progress: Int
process: String
precedingProcess: String
guiAttachmentCount: Int
extendedStatus: String
createdAt: DateTime!
Expand Down
68 changes: 68 additions & 0 deletions docs/schema/V1/swagger.verified.json
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,16 @@
"example": "urn:altinn:person:identifier-no:01125512345\nurn:altinn:organization:identifier-no:912345678",
"type": "string"
},
"precedingProcess": {
"description": "Optional preceding process identifier to indicate the business process that preceded the process indicated in the \u0022Process\u0022 field. Cannot be set without also \u0022Process\u0022 being set.",
"nullable": true,
"type": "string"
},
"process": {
"description": "Optional process identifier used to indicate a business process this dialog belongs to ",
"nullable": true,
"type": "string"
},
"progress": {
"description": "Advisory indicator of progress, represented as 1-100 percentage value. 100% representing a dialog that has come\nto a natural completion (successful or not).",
"format": "int32",
Expand Down Expand Up @@ -2035,6 +2045,16 @@
"example": "urn:altinn:person:identifier-no:01125512345\nurn:altinn:organization:identifier-no:912345678",
"type": "string"
},
"precedingProcess": {
"description": "Optional preceding process identifier to indicate the business process that preceded the process indicated in the \u0022Process\u0022 field. Cannot be set without also \u0022Process\u0022 being set.",
"nullable": true,
"type": "string"
},
"process": {
"description": "Optional process identifier used to indicate a business process this dialog belongs to",
"nullable": true,
"type": "string"
},
"progress": {
"description": "Advisory indicator of progress, represented as 1-100 percentage value. 100% representing a dialog that has come\nto a natural completion (successful or not).",
"format": "int32",
Expand Down Expand Up @@ -2178,6 +2198,16 @@
"example": "urn:altinn:person:identifier-no:01125512345\nurn:altinn:organization:identifier-no:912345678",
"type": "string"
},
"precedingProcess": {
"description": "Optional preceding process identifier to indicate the business process that preceded the process indicated in the \u0022Process\u0022 field. Cannot be set without also \u0022Process\u0022 being set. ",
"nullable": true,
"type": "string"
},
"process": {
"description": "Optional process identifier used to indicate a business process this dialog belongs to ",
"nullable": true,
"type": "string"
},
"progress": {
"description": "Advisory indicator of progress, represented as 1-100 percentage value. 100% representing a dialog that has come\nto a natural completion (successful or not).",
"format": "int32",
Expand Down Expand Up @@ -3108,6 +3138,16 @@
"example": "urn:altinn:person:identifier-no:01125512345\nurn:altinn:organization:identifier-no:912345678",
"type": "string"
},
"precedingProcess": {
"description": "Optional preceding process identifier to indicate the business process that preceded the process indicated in the \u0022Process\u0022 field. Cannot be set without also \u0022Process\u0022 being set. ",
"nullable": true,
"type": "string"
},
"process": {
"description": "Optional process identifier used to indicate a business process this dialog belongs to ",
"nullable": true,
"type": "string"
},
"progress": {
"description": "Advisory indicator of progress, represented as 1-100 percentage value. 100% representing a dialog that has come\nto a natural completion (successful or not).",
"format": "int32",
Expand Down Expand Up @@ -3207,6 +3247,16 @@
"example": "urn:altinn:person:identifier-no:01125512345\nurn:altinn:organization:identifier-no:912345678",
"type": "string"
},
"precedingProcess": {
"description": "Optional preceding process identifier to indicate the business process that preceded the process indicated in the \u0022Process\u0022 field. Cannot be set without also \u0022Process\u0022 being set.",
"nullable": true,
"type": "string"
},
"process": {
"description": "Optional process identifier used to indicate a business process this dialog belongs to",
"nullable": true,
"type": "string"
},
"progress": {
"description": "Advisory indicator of progress, represented as 1-100 percentage value. 100% representing a dialog that has come\nto a natural completion (successful or not).",
"format": "int32",
Expand Down Expand Up @@ -4170,6 +4220,15 @@
"type": "string"
}
},
{
"description": "Filter by process",
"in": "query",
"name": "process",
"schema": {
"nullable": true,
"type": "string"
}
},
{
"description": "Search string for free text search. Will attempt to fuzzily match in all free text fields in the aggregate",
"in": "query",
Expand Down Expand Up @@ -4821,6 +4880,15 @@
"type": "string"
}
},
{
"description": "Filter by process",
"in": "query",
"name": "process",
"schema": {
"nullable": true,
"type": "string"
}
},
{
"description": "Search string for free text search. Will attempt to fuzzily match in all free text fields in the aggregate",
"in": "query",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ private static Dictionary<string, object> GetCloudEventData(DialogActivityCreate
data["relatedActivityId"] = domainEvent.RelatedActivityId.ToString()!;
}

if (domainEvent.Process is not null)
{
data["process"] = domainEvent.Process;
}
if (domainEvent.PrecedingProcess is not null)
{
data["precedingProcess"] = domainEvent.PrecedingProcess;
}
return data;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ public async Task Handle(DialogCreatedDomainEvent domainEvent, CancellationToken
Resource = domainEvent.ServiceResource,
ResourceInstance = domainEvent.DialogId.ToString(),
Subject = domainEvent.Party,
Source = $"{SourceBaseUrl()}{domainEvent.DialogId}"
Source = $"{SourceBaseUrl()}{domainEvent.DialogId}",
Data = GetCloudEventData(domainEvent)
};
await CloudEventBus.Publish(cloudEvent, cancellationToken);
}
Expand All @@ -39,7 +40,8 @@ public async Task Handle(DialogUpdatedDomainEvent domainEvent, CancellationToken
Resource = domainEvent.ServiceResource,
ResourceInstance = domainEvent.DialogId.ToString(),
Subject = domainEvent.Party,
Source = $"{SourceBaseUrl()}{domainEvent.DialogId}"
Source = $"{SourceBaseUrl()}{domainEvent.DialogId}",
Data = GetCloudEventData(domainEvent)
};

await CloudEventBus.Publish(cloudEvent, cancellationToken);
Expand All @@ -55,7 +57,8 @@ public async Task Handle(DialogSeenDomainEvent domainEvent, CancellationToken ca
Resource = domainEvent.ServiceResource,
ResourceInstance = domainEvent.DialogId.ToString(),
Subject = domainEvent.Party,
Source = $"{SourceBaseUrl()}{domainEvent.DialogId}"
Source = $"{SourceBaseUrl()}{domainEvent.DialogId}",
Data = GetCloudEventData(domainEvent)
};

await CloudEventBus.Publish(cloudEvent, cancellationToken);
Expand All @@ -71,9 +74,24 @@ public async Task Handle(DialogDeletedDomainEvent domainEvent, CancellationToken
Resource = domainEvent.ServiceResource,
ResourceInstance = domainEvent.DialogId.ToString(),
Subject = domainEvent.Party,
Source = $"{SourceBaseUrl()}{domainEvent.DialogId}"
Source = $"{SourceBaseUrl()}{domainEvent.DialogId}",
Data = GetCloudEventData(domainEvent)
};

await CloudEventBus.Publish(cloudEvent, cancellationToken);
}

private static Dictionary<string, object>? GetCloudEventData(IProcessEvent domainEvent)
{
var data = new Dictionary<string, object>();
if (domainEvent.Process is not null)
{
data["process"] = domainEvent.Process;
}
if (domainEvent.PrecedingProcess is not null)
{
data["precedingProcess"] = domainEvent.PrecedingProcess;
}
return data.Count == 0 ? null : data;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ public sealed class GetDialogDto
/// </summary>
public int? Progress { get; set; }

/// <summary>
/// Optional process identifier used to indicate a business process this dialog belongs to
/// </summary>
public string? Process { get; set; }

/// <summary>
/// Optional preceding process identifier to indicate the business process that preceded the process indicated in the "Process" field. Cannot be set without also "Process" being set.
/// </summary>
public string? PrecedingProcess { get; set; }

/// <summary>
/// Arbitrary string with a service-specific indicator of status, typically used to indicate a fine-grained state of
/// the dialog to further specify the "status" enum.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ public class SearchDialogDtoBase
/// </summary>
public int? Progress { get; set; }

/// <summary>
/// Optional process identifier used to indicate a business process this dialog belongs to
/// </summary>
public string? Process { get; set; }

/// <summary>
/// Optional preceding process identifier to indicate the business process that preceded the process indicated in the "Process" field. Cannot be set without also "Process" being set.
/// </summary>
public string? PrecedingProcess { get; set; }

/// <summary>
/// The number of attachments in the dialog made available for browser-based frontends.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ public sealed class SearchDialogQuery : SortablePaginationParameter<SearchDialog
/// </summary>
public DateTimeOffset? DueBefore { get; init; }

/// <summary>
/// Filter by process
/// </summary>
public string? Process { get; init; }

/// <summary>
/// Search string for free text search. Will attempt to fuzzily match in all free text fields in the aggregate
/// </summary>
Expand Down Expand Up @@ -150,7 +155,7 @@ public async Task<SearchDialogResult> Handle(SearchDialogQuery request, Cancella
.AsSingleQuery()
.AsNoTracking()
.Include(x => x.Content)
.ThenInclude(x => x.Value.Localizations)
.ThenInclude(x => x.Value.Localizations)
.WhereIf(!request.Org.IsNullOrEmpty(), x => request.Org!.Contains(x.Org))
.WhereIf(!request.ServiceResource.IsNullOrEmpty(), x => request.ServiceResource!.Contains(x.ServiceResource))
.WhereIf(!request.Party.IsNullOrEmpty(), x => request.Party!.Contains(x.Party))
Expand All @@ -164,6 +169,7 @@ public async Task<SearchDialogResult> Handle(SearchDialogQuery request, Cancella
.WhereIf(request.UpdatedBefore.HasValue, x => x.UpdatedAt <= request.UpdatedBefore)
.WhereIf(request.DueAfter.HasValue, x => request.DueAfter <= x.DueAt)
.WhereIf(request.DueBefore.HasValue, x => x.DueAt <= request.DueBefore)
.WhereIf(request.Process is not null, x => EF.Functions.ILike(x.Process!, request.Process!))
.WhereIf(request.Search is not null, x =>
x.Content.Any(x => x.Value.Localizations.AsQueryable().Any(searchExpression)) ||
x.SearchTags.Any(x => EF.Functions.ILike(x.Value, request.Search!))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ public SearchDialogQueryValidator()
.LessThanOrEqualTo(20)
.When(x => x.ExtendedStatus is not null);

RuleFor(x => x.Process)
.Must(x => Uri.IsWellFormedUriString(x, UriKind.Absolute))
.WithMessage("{PropertyName} must be a valid URI")
.When(x => x.Process is not null);

RuleForEach(x => x.Status).IsInEnum();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public CreateDialogCommandValidator(
.MaximumLength(Constants.DefaultMaxUriLength)
.Must(x =>
x?.StartsWith(Constants.ServiceResourcePrefix, StringComparison.InvariantCulture) ?? false)
.WithMessage($"'{{PropertyName}}' must start with '{Constants.ServiceResourcePrefix}'.");
.WithMessage($"'{{PropertyName}}' must start with '{Constants.ServiceResourcePrefix}'.");

RuleFor(x => x.Party)
.IsValidPartyIdentifier()
Expand All @@ -56,16 +56,16 @@ public CreateDialogCommandValidator(
RuleFor(x => x.ExpiresAt)
.IsInFuture()
.GreaterThanOrEqualTo(x => x.DueAt)
.WithMessage(FluentValidationDateTimeOffsetExtensions.InFutureOfMessage)
.When(x => x.DueAt.HasValue, ApplyConditionTo.CurrentValidator)
.WithMessage(FluentValidationDateTimeOffsetExtensions.InFutureOfMessage)
.When(x => x.DueAt.HasValue, ApplyConditionTo.CurrentValidator)
.GreaterThanOrEqualTo(x => x.VisibleFrom)
.WithMessage(FluentValidationDateTimeOffsetExtensions.InFutureOfMessage)
.When(x => x.VisibleFrom.HasValue, ApplyConditionTo.CurrentValidator);
.WithMessage(FluentValidationDateTimeOffsetExtensions.InFutureOfMessage)
.When(x => x.VisibleFrom.HasValue, ApplyConditionTo.CurrentValidator);
RuleFor(x => x.DueAt)
.IsInFuture()
.GreaterThanOrEqualTo(x => x.VisibleFrom)
.WithMessage(FluentValidationDateTimeOffsetExtensions.InFutureOfMessage)
.When(x => x.VisibleFrom.HasValue, ApplyConditionTo.CurrentValidator);
.WithMessage(FluentValidationDateTimeOffsetExtensions.InFutureOfMessage)
.When(x => x.VisibleFrom.HasValue, ApplyConditionTo.CurrentValidator);
RuleFor(x => x.VisibleFrom)
.IsInFuture();

Expand All @@ -83,15 +83,15 @@ public CreateDialogCommandValidator(
.Must(x => x
.EmptyIfNull()
.Count(x => x.Priority == DialogGuiActionPriority.Values.Primary) <= 1)
.WithMessage("Only one primary GUI action is allowed.")
.WithMessage("Only one primary GUI action is allowed.")
.Must(x => x
.EmptyIfNull()
.Count(x => x.Priority == DialogGuiActionPriority.Values.Secondary) <= 1)
.WithMessage("Only one secondary GUI action is allowed.")
.WithMessage("Only one secondary GUI action is allowed.")
.Must(x => x
.EmptyIfNull()
.Count(x => x.Priority == DialogGuiActionPriority.Values.Tertiary) <= 5)
.WithMessage("Only five tertiary GUI actions are allowed.")
.WithMessage("Only five tertiary GUI actions are allowed.")
.ForEach(x => x.SetValidator(guiActionValidator));

RuleForEach(x => x.ApiActions)
Expand All @@ -118,6 +118,20 @@ public CreateDialogCommandValidator(
dependentKeySelector: activity => activity.RelatedActivityId,
principalKeySelector: activity => activity.Id)
.SetValidator(activityValidator);
RuleFor(x => x.Process)
.Must(x => Uri.IsWellFormedUriString(x, UriKind.Absolute))
.WithMessage("{PropertyName} must be a valid absolute URI.")
.When(x => x.Process is not null);

RuleFor(x => x.Process)
.NotEmpty()
.WithMessage($"{{PropertyName}} must not be empty when {nameof(CreateDialogCommand.PrecedingProcess)} is set.")
.When(x => x.PrecedingProcess is not null);

RuleFor(x => x.PrecedingProcess)
.Must(x => Uri.IsWellFormedUriString(x, UriKind.Absolute))
.WithMessage("{PropertyName} must be a valid absolute URI.")
.When(x => x.PrecedingProcess is not null);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ public class CreateDialogDto
/// <example>2022-12-31T23:59:59Z</example>
public DateTimeOffset? DueAt { get; set; }

/// <summary>
/// Optional process identifier used to indicate a business process this dialog belongs to
/// </summary>
public string? Process { get; set; }
/// <summary>
/// Optional preceding process identifier to indicate the business process that preceded the process indicated in the "Process" field. Cannot be set without also "Process" being set.
/// </summary>
public string? PrecedingProcess { get; set; }

/// <summary>
/// The expiration date for the dialog. This is the last date when the dialog is available for the end user.
///
Expand Down
Loading

0 comments on commit 2bf0d30

Please sign in to comment.