Skip to content

Commit 5f74da2

Browse files
authored
Merge pull request #296 from KodrAus/feat/appinstance-stream
Support streaming incoming events when creating app instances
2 parents 124a16f + 28a4070 commit 5f74da2

File tree

7 files changed

+215
-5
lines changed

7 files changed

+215
-5
lines changed

global.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
"sdk": {
33
"version": "8.0.100-rc.2.23502.2"
44
}
5-
}
5+
}

src/SeqCli/Cli/Commands/AppInstance/CreateCommand.cs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using SeqCli.Cli.Features;
77
using SeqCli.Config;
88
using SeqCli.Connection;
9+
using SeqCli.Signals;
910
using SeqCli.Util;
1011
using Serilog;
1112

@@ -20,9 +21,10 @@ class CreateCommand : Command
2021
readonly ConnectionFeature _connection;
2122
readonly OutputFormatFeature _output;
2223

23-
string? _title, _appId;
24+
string? _title, _appId, _streamIncomingEventsSignal;
2425
readonly Dictionary<string, string> _settings = new();
2526
readonly List<string> _overridable = new();
27+
bool _streamIncomingEvents;
2628

2729
public CreateCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config)
2830
{
@@ -48,12 +50,24 @@ public CreateCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config
4850
_settings.Add(name, valueText ?? "");
4951
});
5052

53+
Options.Add(
54+
"stream:",
55+
"Stream incoming events to this app instance as they're ingested; optionally accepts a signal expression limiting which events should be streamed",
56+
s =>
57+
{
58+
_streamIncomingEvents = true;
59+
60+
if (!string.IsNullOrEmpty(s))
61+
{
62+
_streamIncomingEventsSignal = s;
63+
}
64+
}
65+
);
66+
5167
Options.Add(
5268
"overridable=",
5369
"Specify setting names that may be overridden by users when invoking the app",
5470
s => _overridable.Add(s));
55-
56-
// The command doesn't yet implement "Stream incoming events".
5771

5872
_connection = Enable<ConnectionFeature>();
5973
_output = Enable(new OutputFormatFeature(config.Output));
@@ -77,6 +91,8 @@ bool ValidateSettingName(string settingName)
7791
}
7892

7993
instance.Title = _title;
94+
instance.AcceptStreamedEvents = _streamIncomingEvents;
95+
instance.StreamedSignalExpression = !string.IsNullOrWhiteSpace(_streamIncomingEventsSignal) ? SignalExpressionParser.ParseExpression(_streamIncomingEventsSignal) : null;
8096

8197
foreach (var setting in _settings)
8298
{
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright 2018 Datalust Pty Ltd
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using Seq.Api.Model.Signals;
16+
using Superpower;
17+
using Superpower.Parsers;
18+
19+
namespace SeqCli.Signals;
20+
21+
static class SignalExpressionParser
22+
{
23+
// intersect: `,`
24+
static readonly TokenListParser<SignalExpressionToken, SignalExpressionKind> Intersect =
25+
Token.EqualTo(SignalExpressionToken.Comma).Value(SignalExpressionKind.Intersection);
26+
27+
// union: `~`
28+
static readonly TokenListParser<SignalExpressionToken, SignalExpressionKind> Union =
29+
Token.EqualTo(SignalExpressionToken.Tilde).Value(SignalExpressionKind.Union);
30+
31+
// operator: `intersect` | `union`
32+
static readonly TokenListParser<SignalExpressionToken, SignalExpressionKind> Operator = Intersect.Or(Union);
33+
34+
// concrete: `signalid`
35+
static readonly TokenListParser<SignalExpressionToken, SignalExpressionPart> SignalId =
36+
from id in Token.EqualTo(SignalExpressionToken.Id)
37+
select SignalExpressionPart.Signal(id.ToStringValue());
38+
39+
// brackets: `(` => `operation` => `)`
40+
static readonly TokenListParser<SignalExpressionToken, SignalExpressionPart> Brackets =
41+
from lparen in Token.EqualTo(SignalExpressionToken.LParen)
42+
from expr in Parse.Ref(() => Operation!)
43+
from rparen in Token.EqualTo(SignalExpressionToken.RParen)
44+
select expr;
45+
46+
// subexpr: `brackets` | `id`
47+
static readonly TokenListParser<SignalExpressionToken, SignalExpressionPart> SubExpression =
48+
Brackets.Or(SignalId).Named("expression");
49+
50+
// operation: ( `subexpr` => `operator` => `subexpr` )*
51+
static readonly TokenListParser<SignalExpressionToken, SignalExpressionPart> Operation =
52+
Parse.Chain(Operator, SubExpression, (kind, lhs, rhs) => new SignalExpressionPart
53+
{
54+
Kind = kind,
55+
Left = lhs,
56+
Right = rhs
57+
});
58+
59+
static readonly TokenListParser<SignalExpressionToken, SignalExpressionPart> SignalExpression = Operation.AtEnd();
60+
61+
static readonly SignalExpressionTokenizer Tokenizer = new();
62+
63+
public static SignalExpressionPart ParseExpression(string input)
64+
{
65+
var tokens = Tokenizer.Tokenize(input);
66+
return SignalExpression.Parse(tokens);
67+
}
68+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2018 Datalust Pty Ltd
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using Superpower.Display;
16+
17+
namespace SeqCli.Signals;
18+
19+
enum SignalExpressionToken
20+
{
21+
Id,
22+
[Token(Category = "operator", Example = ",")]
23+
Comma,
24+
[Token(Category = "operator", Example = "~")]
25+
Tilde,
26+
[Token(Example = "(")]
27+
LParen,
28+
[Token(Example = ")")]
29+
RParen
30+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright 2018 Datalust Pty Ltd
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using System.Collections.Generic;
16+
using Superpower;
17+
using Superpower.Model;
18+
using Superpower.Parsers;
19+
20+
namespace SeqCli.Signals;
21+
22+
class SignalExpressionTokenizer : Tokenizer<SignalExpressionToken>
23+
{
24+
static readonly Dictionary<char, SignalExpressionToken> Operators = new()
25+
{
26+
[','] = SignalExpressionToken.Comma,
27+
['~'] = SignalExpressionToken.Tilde,
28+
['('] = SignalExpressionToken.LParen,
29+
[')'] = SignalExpressionToken.RParen
30+
};
31+
32+
static readonly TextParser<TextSpan> Id = Span.WithoutAny(ch => Operators.ContainsKey(ch) || char.IsWhiteSpace(ch));
33+
34+
protected override IEnumerable<Result<SignalExpressionToken>> Tokenize(TextSpan span)
35+
{
36+
var next = SkipWhiteSpace(span);
37+
if (!next.HasValue)
38+
{
39+
yield break;
40+
}
41+
42+
do
43+
{
44+
if (Operators.TryGetValue(next.Value, out var token))
45+
{
46+
yield return Result.Value(token, next.Location, next.Remainder);
47+
next = next.Remainder.ConsumeChar();
48+
}
49+
else
50+
{
51+
var id = Id(next.Location);
52+
next = id.Remainder.ConsumeChar();
53+
54+
yield return Result.Value(SignalExpressionToken.Id, id.Location, id.Remainder);
55+
}
56+
57+
next = SkipWhiteSpace(next.Location);
58+
} while (next.HasValue);
59+
}
60+
}

test/SeqCli.EndToEnd/AppInstance/AppInstanceBasicsTestCase.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public async Task ExecuteAsync(SeqConnection connection, ILogger logger, CliComm
2121
var app = (await connection.Apps.ListAsync()).Single();
2222

2323
var title = Guid.NewGuid().ToString("N");
24-
exit = runner.Exec("appinstance create", $"-t {title} --app {app.Id} -p [email protected] -p [email protected] -p Host=localhost");
24+
exit = runner.Exec("appinstance create", $"-t {title} --app {app.Id} --stream -p [email protected] -p [email protected] -p Host=localhost");
2525
Assert.Equal(0, exit);
2626

2727
var appInstance = (await connection.AppInstances.ListAsync()).Single();
@@ -41,5 +41,9 @@ public async Task ExecuteAsync(SeqConnection connection, ILogger logger, CliComm
4141

4242
exit = runner.Exec("appinstance list", $"-t {title}");
4343
Assert.Equal(0, exit);
44+
45+
var streamSignal = "signal-m33303,(signal-m33301~signal-m33302)";
46+
exit = runner.Exec("appinstance create", $"-t {Guid.NewGuid():N} --app {app.Id} --stream=\"{streamSignal}\" -p [email protected] -p [email protected] -p Host=localhost");
47+
Assert.Equal(0, exit);
4448
}
4549
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System.Collections.Generic;
2+
using SeqCli.Signals;
3+
using Xunit;
4+
5+
namespace SeqCli.Tests.Signals;
6+
7+
public class SignalExpressionParserTests
8+
{
9+
[Theory, MemberData(nameof(_sources))]
10+
public void ParseSuccessfully((string, string) inputs)
11+
{
12+
var (input, expected) = inputs;
13+
14+
var parsed = SignalExpressionParser.ParseExpression(input).ToString();
15+
16+
Assert.Equal(expected, parsed);
17+
}
18+
19+
public static IEnumerable<object[]> _sources = new []{
20+
new object[] { ("signal-1 ", "signal-1") },
21+
22+
new object[] { ("(signal-1)", "signal-1") },
23+
24+
new object[] { ("signal-1 ,signal-2", "signal-1,signal-2") },
25+
26+
new object[] { (" signal-1,signal-2~ signal-3", "(signal-1,signal-2)~signal-3") },
27+
28+
new object[] { ("signal-1,signal-2,(signal-3~signal-4)", "(signal-1,signal-2),(signal-3~signal-4)") },
29+
30+
new object[] { ("signal-1~( (signal-2~signal-3) ,signal-4)", "signal-1~((signal-2~signal-3),signal-4)") }
31+
};
32+
}

0 commit comments

Comments
 (0)