Skip to content

Commit 157c999

Browse files
Support projects_v2_item changes of scalar values like strings/dates/numbers (#725)
Co-authored-by: Jamie Magee <[email protected]>
1 parent 45612c4 commit 157c999

File tree

10 files changed

+296
-7
lines changed

10 files changed

+296
-7
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
namespace Octokit.Webhooks.Converter;
2+
3+
using Octokit.Webhooks.Models.ProjectsV2ItemEvent;
4+
5+
public class ChangesFieldValueChangeConverter : JsonConverter<ChangesFieldValueChangeBase>
6+
{
7+
public override ChangesFieldValueChangeBase? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
8+
{
9+
switch (reader)
10+
{
11+
case { TokenType: JsonTokenType.StartObject }:
12+
var changeObject = JsonSerializer.Deserialize<ChangesFieldValueChange>(ref reader, options);
13+
return changeObject;
14+
case { TokenType: JsonTokenType.String }:
15+
return new ChangesFieldValueScalarChange { StringValue = reader.GetString() };
16+
case { TokenType: JsonTokenType.Null }:
17+
return null;
18+
case { TokenType: JsonTokenType.Number }:
19+
return new ChangesFieldValueScalarChange { NumericValue = reader.GetDecimal() };
20+
default:
21+
throw new JsonException($"Invalid JsonTokenType {reader.TokenType}");
22+
}
23+
}
24+
25+
public override void Write(Utf8JsonWriter writer, ChangesFieldValueChangeBase value, JsonSerializerOptions options)
26+
{
27+
switch (value)
28+
{
29+
case ChangesFieldValueScalarChange { StringValue: not null } scalarChange:
30+
writer.WriteStringValue(scalarChange.StringValue);
31+
break;
32+
case ChangesFieldValueScalarChange { NumericValue: not null } scalarChange:
33+
writer.WriteNumberValue(scalarChange.NumericValue.Value);
34+
break;
35+
case ChangesFieldValueScalarChange:
36+
writer.WriteNullValue();
37+
break;
38+
case ChangesFieldValueChange change:
39+
JsonSerializer.Serialize(writer, change, options);
40+
break;
41+
default:
42+
writer.WriteNullValue();
43+
break;
44+
}
45+
}
46+
}

src/Octokit.Webhooks/Models/ProjectsV2ItemEvent/ChangesFieldValue.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ public sealed record ChangesFieldValue
1717
public long ProjectNumber { get; init; }
1818

1919
[JsonPropertyName("from")]
20-
public ChangesFieldValueChange From { get; init; } = null!;
20+
public ChangesFieldValueChangeBase From { get; init; } = null!;
2121

2222
[JsonPropertyName("to")]
23-
public ChangesFieldValueChange To { get; init; } = null!;
23+
public ChangesFieldValueChangeBase To { get; init; } = null!;
2424
}

src/Octokit.Webhooks/Models/ProjectsV2ItemEvent/ChangesFieldValueChange.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
namespace Octokit.Webhooks.Models.ProjectsV2ItemEvent;
22

33
[PublicAPI]
4-
public sealed record ChangesFieldValueChange
4+
public sealed record ChangesFieldValueChange : ChangesFieldValueChangeBase
55
{
66
[JsonPropertyName("id")]
77
public string Id { get; init; } = null!;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Octokit.Webhooks.Models.ProjectsV2ItemEvent;
2+
3+
[JsonConverter(typeof(ChangesFieldValueChangeConverter))]
4+
public abstract record ChangesFieldValueChangeBase
5+
{
6+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace Octokit.Webhooks.Models.ProjectsV2ItemEvent;
2+
3+
[PublicAPI]
4+
public sealed record ChangesFieldValueScalarChange : ChangesFieldValueChangeBase
5+
{
6+
public string? StringValue { get; init; }
7+
8+
public decimal? NumericValue { get; init; }
9+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
namespace Octokit.Webhooks.Test.Converter;
2+
3+
using System.Text.Json;
4+
using System.Text.Json.Serialization;
5+
using AwesomeAssertions;
6+
using Octokit.Webhooks.Converter;
7+
using Octokit.Webhooks.Models.ProjectsV2ItemEvent;
8+
using Xunit;
9+
10+
public class ChangesFieldValueChangeConverterTests(ITestOutputHelper output)
11+
{
12+
private readonly JsonSerializerOptions options = new()
13+
{
14+
WriteIndented = true,
15+
Converters =
16+
{
17+
new ChangesFieldValueChangeConverter(),
18+
},
19+
};
20+
21+
[Fact]
22+
public void Roundtrip()
23+
{
24+
var test = new TestObject
25+
{
26+
AsString = new ChangesFieldValueScalarChange { StringValue = "Hello world" },
27+
AsNumber = new ChangesFieldValueScalarChange { NumericValue = 3.1415926m },
28+
AsObject = new ChangesFieldValueChange
29+
{
30+
Color = "color",
31+
Description = "description",
32+
Id = "12345",
33+
Name = "Name",
34+
},
35+
};
36+
37+
var serialized = JsonSerializer.Serialize(test, this.options);
38+
output.WriteLine(serialized);
39+
40+
var deserialized = JsonSerializer.Deserialize<TestObject>(serialized, this.options);
41+
42+
deserialized.Should().NotBeNull();
43+
deserialized.AsString.Should().NotBeNull().And.BeOfType<ChangesFieldValueScalarChange>();
44+
deserialized.AsNumber.Should().NotBeNull().And.BeOfType<ChangesFieldValueScalarChange>();
45+
46+
(deserialized.AsString as ChangesFieldValueScalarChange)?.StringValue.Should().Be("Hello world");
47+
(deserialized.AsString as ChangesFieldValueScalarChange)?.NumericValue.Should().BeNull();
48+
49+
(deserialized.AsNumber as ChangesFieldValueScalarChange)?.StringValue.Should().BeNull();
50+
(deserialized.AsNumber as ChangesFieldValueScalarChange)?.NumericValue.Should().Be(3.1415926m);
51+
52+
deserialized.AsObject.Should().NotBeNull().And.BeOfType<ChangesFieldValueChange>();
53+
var changeObject = deserialized.AsObject.As<ChangesFieldValueChange>();
54+
55+
changeObject.Color.Should().Be("color");
56+
changeObject.Description.Should().Be("description");
57+
changeObject.Id.Should().Be("12345");
58+
changeObject.Name.Should().Be("Name");
59+
}
60+
61+
internal sealed class TestObject
62+
{
63+
[JsonPropertyName("as_string")]
64+
public ChangesFieldValueChangeBase AsString { get; init; } = null!;
65+
66+
[JsonPropertyName("as_number")]
67+
public ChangesFieldValueChangeBase AsNumber { get; init; } = null!;
68+
69+
[JsonPropertyName("as_object")]
70+
public ChangesFieldValueChangeBase AsObject { get; init; } = null!;
71+
}
72+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
{
2+
"action": "edited",
3+
"projects_v2_item": {
4+
"id": 5678510,
5+
"node_id": "PVTI_lADOAWcTxs07G84AVqWu",
6+
"project_node_id": "PVT_kwDOAWcTxs07Gw",
7+
"content_node_id": "DI_lADOAWcTxs07G84AIjOy",
8+
"content_type": "DraftIssue",
9+
"creator": {
10+
"login": "wolfy1339",
11+
"id": 4595477,
12+
"node_id": "MDQ6VXNlcjQ1OTU0Nzc=",
13+
"avatar_url": "https://avatars.githubusercontent.com/u/4595477?v=4",
14+
"gravatar_id": "",
15+
"url": "https://api.github.com/users/wolfy1339",
16+
"html_url": "https://github.com/wolfy1339",
17+
"followers_url": "https://api.github.com/users/wolfy1339/followers",
18+
"following_url": "https://api.github.com/users/wolfy1339/following{/other_user}",
19+
"gists_url": "https://api.github.com/users/wolfy1339/gists{/gist_id}",
20+
"starred_url": "https://api.github.com/users/wolfy1339/starred{/owner}{/repo}",
21+
"subscriptions_url": "https://api.github.com/users/wolfy1339/subscriptions",
22+
"organizations_url": "https://api.github.com/users/wolfy1339/orgs",
23+
"repos_url": "https://api.github.com/users/wolfy1339/repos",
24+
"events_url": "https://api.github.com/users/wolfy1339/events{/privacy}",
25+
"received_events_url": "https://api.github.com/users/wolfy1339/received_events",
26+
"type": "User",
27+
"site_admin": false
28+
},
29+
"created_at": "2022-06-08T20:34:26Z",
30+
"updated_at": "2022-06-08T20:34:26Z",
31+
"archived_at": null
32+
},
33+
"changes": {
34+
"field_value": {
35+
"field_node_id": "PVTF_lADOAEzhac4AjFojzgbzsKM",
36+
"field_type": "date",
37+
"field_name": "Start Date",
38+
"project_number": 233,
39+
"from": null,
40+
"to": "2025-08-01T00:00:00+00:00"
41+
}
42+
},
43+
"organization": {
44+
"login": "Octocoders",
45+
"id": 38302899,
46+
"node_id": "MDEyOk9yZ2FuaXphdGlvbjM4MzAyODk5",
47+
"url": "https://api.github.com/orgs/Octocoders",
48+
"repos_url": "https://api.github.com/orgs/Octocoders/repos",
49+
"events_url": "https://api.github.com/orgs/Octocoders/events",
50+
"hooks_url": "https://api.github.com/orgs/Octocoders/hooks",
51+
"issues_url": "https://api.github.com/orgs/Octocoders/issues",
52+
"members_url": "https://api.github.com/orgs/Octocoders/members{/member}",
53+
"public_members_url": "https://api.github.com/orgs/Octocoders/public_members{/member}",
54+
"avatar_url": "https://avatars1.githubusercontent.com/u/38302899?v=4",
55+
"description": ""
56+
},
57+
"sender": {
58+
"login": "wolfy1339",
59+
"id": 4595477,
60+
"node_id": "MDQ6VXNlcjQ1OTU0Nzc=",
61+
"avatar_url": "https://avatars.githubusercontent.com/u/4595477?v=4",
62+
"gravatar_id": "",
63+
"url": "https://api.github.com/users/wolfy1339",
64+
"html_url": "https://github.com/wolfy1339",
65+
"followers_url": "https://api.github.com/users/wolfy1339/followers",
66+
"following_url": "https://api.github.com/users/wolfy1339/following{/other_user}",
67+
"gists_url": "https://api.github.com/users/wolfy1339/gists{/gist_id}",
68+
"starred_url": "https://api.github.com/users/wolfy1339/starred{/owner}{/repo}",
69+
"subscriptions_url": "https://api.github.com/users/wolfy1339/subscriptions",
70+
"organizations_url": "https://api.github.com/users/wolfy1339/orgs",
71+
"repos_url": "https://api.github.com/users/wolfy1339/repos",
72+
"events_url": "https://api.github.com/users/wolfy1339/events{/privacy}",
73+
"received_events_url": "https://api.github.com/users/wolfy1339/received_events",
74+
"type": "User",
75+
"site_admin": false
76+
}
77+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
{
2+
"action": "edited",
3+
"projects_v2_item": {
4+
"id": 5678510,
5+
"node_id": "PVTI_lADOAWcTxs07G84AVqWu",
6+
"project_node_id": "PVT_kwDOAWcTxs07Gw",
7+
"content_node_id": "DI_lADOAWcTxs07G84AIjOy",
8+
"content_type": "DraftIssue",
9+
"creator": {
10+
"login": "wolfy1339",
11+
"id": 4595477,
12+
"node_id": "MDQ6VXNlcjQ1OTU0Nzc=",
13+
"avatar_url": "https://avatars.githubusercontent.com/u/4595477?v=4",
14+
"gravatar_id": "",
15+
"url": "https://api.github.com/users/wolfy1339",
16+
"html_url": "https://github.com/wolfy1339",
17+
"followers_url": "https://api.github.com/users/wolfy1339/followers",
18+
"following_url": "https://api.github.com/users/wolfy1339/following{/other_user}",
19+
"gists_url": "https://api.github.com/users/wolfy1339/gists{/gist_id}",
20+
"starred_url": "https://api.github.com/users/wolfy1339/starred{/owner}{/repo}",
21+
"subscriptions_url": "https://api.github.com/users/wolfy1339/subscriptions",
22+
"organizations_url": "https://api.github.com/users/wolfy1339/orgs",
23+
"repos_url": "https://api.github.com/users/wolfy1339/repos",
24+
"events_url": "https://api.github.com/users/wolfy1339/events{/privacy}",
25+
"received_events_url": "https://api.github.com/users/wolfy1339/received_events",
26+
"type": "User",
27+
"site_admin": false
28+
},
29+
"created_at": "2022-06-08T20:34:26Z",
30+
"updated_at": "2022-06-08T20:34:26Z",
31+
"archived_at": null
32+
},
33+
"changes": {
34+
"field_value": {
35+
"field_node_id": "PVTF_lADOAEzhac4AjFojzgyEIsU",
36+
"field_type": "number",
37+
"field_name": "tempNumber",
38+
"project_number": 233,
39+
"from": null,
40+
"to": 123.456
41+
}
42+
},
43+
"organization": {
44+
"login": "Octocoders",
45+
"id": 38302899,
46+
"node_id": "MDEyOk9yZ2FuaXphdGlvbjM4MzAyODk5",
47+
"url": "https://api.github.com/orgs/Octocoders",
48+
"repos_url": "https://api.github.com/orgs/Octocoders/repos",
49+
"events_url": "https://api.github.com/orgs/Octocoders/events",
50+
"hooks_url": "https://api.github.com/orgs/Octocoders/hooks",
51+
"issues_url": "https://api.github.com/orgs/Octocoders/issues",
52+
"members_url": "https://api.github.com/orgs/Octocoders/members{/member}",
53+
"public_members_url": "https://api.github.com/orgs/Octocoders/public_members{/member}",
54+
"avatar_url": "https://avatars1.githubusercontent.com/u/38302899?v=4",
55+
"description": ""
56+
},
57+
"sender": {
58+
"login": "wolfy1339",
59+
"id": 4595477,
60+
"node_id": "MDQ6VXNlcjQ1OTU0Nzc=",
61+
"avatar_url": "https://avatars.githubusercontent.com/u/4595477?v=4",
62+
"gravatar_id": "",
63+
"url": "https://api.github.com/users/wolfy1339",
64+
"html_url": "https://github.com/wolfy1339",
65+
"followers_url": "https://api.github.com/users/wolfy1339/followers",
66+
"following_url": "https://api.github.com/users/wolfy1339/following{/other_user}",
67+
"gists_url": "https://api.github.com/users/wolfy1339/gists{/gist_id}",
68+
"starred_url": "https://api.github.com/users/wolfy1339/starred{/owner}{/repo}",
69+
"subscriptions_url": "https://api.github.com/users/wolfy1339/subscriptions",
70+
"organizations_url": "https://api.github.com/users/wolfy1339/orgs",
71+
"repos_url": "https://api.github.com/users/wolfy1339/repos",
72+
"events_url": "https://api.github.com/users/wolfy1339/events{/privacy}",
73+
"received_events_url": "https://api.github.com/users/wolfy1339/received_events",
74+
"type": "User",
75+
"site_admin": false
76+
}
77+
}

test/Octokit.Webhooks.Test/WebhookEventProcessorTests.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ public class WebhookEventProcessorTests
1010

1111
[Theory]
1212
[ClassData(typeof(WebhookEventProcessorTestsData))]
13-
public void CanDeserialize(string @event, string payload, Type expectedType)
13+
public void CanDeserialize(string @event, string testName, string payload, Type expectedType)
1414
{
15+
// Only used to make it easier to differentiate test cases for the same event without looking at whole payload
16+
_ = testName;
1517
var headers = new WebhookHeaders
1618
{
1719
Event = @event,

test/Octokit.Webhooks.Test/WebhookEventProcessorTestsData.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ namespace Octokit.Webhooks.Test;
88
using Octokit.Webhooks.TestUtils;
99
using Xunit;
1010

11-
public class WebhookEventProcessorTestsData : IEnumerable<TheoryDataRow<string, string, Type>>
11+
public class WebhookEventProcessorTestsData : IEnumerable<TheoryDataRow<string, string, string, Type>>
1212
{
13-
public IEnumerator<TheoryDataRow<string, string, Type>> GetEnumerator()
13+
public IEnumerator<TheoryDataRow<string, string, string, Type>> GetEnumerator()
1414
{
1515
var resourcesDirectory = ResourceUtils.GetResources();
1616
var files = Directory.GetFiles(resourcesDirectory, "*.json", SearchOption.AllDirectories);
@@ -20,7 +20,7 @@ public IEnumerator<TheoryDataRow<string, string, Type>> GetEnumerator()
2020
var parts = relativeResource.Split(Path.DirectorySeparatorChar);
2121
var expectedType = ClassUtils.GetEventTypeByName(parts[0].ToPascalCase());
2222
var content = ResourceUtils.ReadResource(relativeResource);
23-
yield return new TheoryDataRow<string, string, Type>(parts[0], content, expectedType);
23+
yield return new TheoryDataRow<string, string, string, Type>(parts[0], parts[1], content, expectedType);
2424
}
2525
}
2626

0 commit comments

Comments
 (0)