diff --git a/PluginJira/API/Factory/ApiClient.cs b/PluginJira/API/Factory/ApiClient.cs index 424325b..4b4e9bd 100644 --- a/PluginJira/API/Factory/ApiClient.cs +++ b/PluginJira/API/Factory/ApiClient.cs @@ -61,14 +61,16 @@ public async Task GetAsync(string path) try { var token = await Authenticator.GetToken(); - var uri = new Uri($"{Constants.BaseApiUrl.TrimEnd('/')}/{path.TrimStart('/')}"); + var uri = new Uri($"{Settings.GetBaseUri().TrimEnd('/')}/{path.TrimStart('/')}"); var request = new HttpRequestMessage { Method = HttpMethod.Get, RequestUri = uri, }; - request.Headers.Add(_tokenHeaderName, token); + + // Add basic authentication + request.Headers.Authorization = new AuthenticationHeaderValue("Basic", token); return await Client.SendAsync(request); } diff --git a/PluginJira/API/Utility/Constants.cs b/PluginJira/API/Utility/Constants.cs index 8a78e74..1d55e65 100644 --- a/PluginJira/API/Utility/Constants.cs +++ b/PluginJira/API/Utility/Constants.cs @@ -3,7 +3,7 @@ namespace PluginJira.API.Utility public static class Constants { public static string BaseApiUrl = "https://test.atlassian.net/rest/api/2"; - public static string TestConnectionPath = "/applicationrole"; + public static string TestConnectionPath = "applicationrole"; public static string CustomProperty = "CustomProperty"; public static string EmptySchemaDescription = "This schema has no properties. This is likely due to to there being no data."; } diff --git a/PluginJira/API/Utility/EndpointHelper.cs b/PluginJira/API/Utility/EndpointHelper.cs index cc1884c..401cb40 100644 --- a/PluginJira/API/Utility/EndpointHelper.cs +++ b/PluginJira/API/Utility/EndpointHelper.cs @@ -23,6 +23,7 @@ public static class EndpointHelper static EndpointHelper() { IssuesEndpointHelper.IssuesEndpoints.ToList().ForEach(x => Endpoints.TryAdd(x.Key, x.Value)); + ApplicationRolesEndpointHelper.ApplicationRolesEndpoints.ToList().ForEach(x => Endpoints.TryAdd(x.Key, x.Value)); } public static Dictionary GetAllEndpoints() @@ -65,24 +66,13 @@ public abstract class Endpoint public virtual async Task GetCountOfRecords(IApiClientFactory factory, Settings settings) { - var response = await factory.CreateApiClient(settings).GetAsync($"{BasePath.TrimEnd('/')}/{AllPath.TrimStart('/')}"); - - var recordsList = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - - return new Count - { - Kind = Count.Types.Kind.Exact, - Value = (int) recordsList.TotalRecords - }; + throw new NotImplementedException(); } public virtual IAsyncEnumerable ReadRecordsAsync(IApiClientFactory factory, Settings settings, DateTime? lastReadTime = null, TaskCompletionSource? tcs = null, bool isDiscoverRead = false) { - throw new NotImplementedException(); - - } public virtual async Task WriteRecordAsync(IApiClient apiClient, Schema schema, Record record, diff --git a/PluginJira/API/Utility/EndpointHelperEndpoints/ApplicationRolesEndpoint.cs b/PluginJira/API/Utility/EndpointHelperEndpoints/ApplicationRolesEndpoint.cs new file mode 100644 index 0000000..971e160 --- /dev/null +++ b/PluginJira/API/Utility/EndpointHelperEndpoints/ApplicationRolesEndpoint.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Grpc.Core; +using Naveego.Sdk.Plugins; +using Newtonsoft.Json; +using PluginJira.API.Factory; +using PluginJira.API.Utility.EndpointHelperEndpoints; +using PluginJira.DataContracts; +using PluginJira.Helper; + +namespace PluginJira.API.Utility.EndpointHelperEndpoints +{ + public static class ApplicationRolesEndpointHelper + { + private class ApplicationRolesEndpoint : Endpoint + { + public override async IAsyncEnumerable ReadRecordsAsync(IApiClientFactory factory, Settings settings, + DateTime? lastReadTime = null, TaskCompletionSource? tcs = null, bool isDiscoverRead = false) + { + // fetch all records + var jira = factory.CreateApiClient(settings); + + var response = await jira.GetAsync("applicationrole"); + + var recordsList = JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); + + foreach (var item in recordsList) + { + var recordMap = new Dictionary(); + + recordMap["key"] = item.Key; + recordMap["groups"] = item.Groups; + recordMap["name"]= item.Name; + + yield return new Record + { + Action = Record.Types.Action.Upsert, + DataJson = JsonConvert.SerializeObject(recordMap) + }; + } + + } + + public override async Task GetCountOfRecords(IApiClientFactory factory, Settings settings) + { + var response = await factory.CreateApiClient(settings).GetAsync($"{BasePath.TrimEnd('/')}/{AllPath.TrimStart('/')}"); + + var recordsList = JsonConvert.DeserializeObject >(await response.Content.ReadAsStringAsync()); + + return new Count + { + Kind = Count.Types.Kind.Unavailable + }; + + } + } + + public static readonly Dictionary ApplicationRolesEndpoints = new Dictionary + { + {"AllApplicationRoles", new ApplicationRolesEndpoint + { + Id = "AllApplicationRoles", + Name = "All Application Roles", + BasePath = "/applicationrole", + AllPath = "/", + DetailPath = "/", + DetailPropertyId = "", + SupportedActions = new List + { + EndpointActions.Get + }, + PropertyKeys = new List + { + "BounceID" + } + }}, + }; + } +} \ No newline at end of file diff --git a/PluginJira/API/Utility/EndpointHelperEndpoints/IssuesEndpoint.cs b/PluginJira/API/Utility/EndpointHelperEndpoints/IssuesEndpoint.cs index 6991688..797daec 100644 --- a/PluginJira/API/Utility/EndpointHelperEndpoints/IssuesEndpoint.cs +++ b/PluginJira/API/Utility/EndpointHelperEndpoints/IssuesEndpoint.cs @@ -24,38 +24,66 @@ public override async IAsyncEnumerable ReadRecordsAsync(IApiClientFactor // fetch all records var jira = factory.CreateJiraClient(settings); - // var issues = jira.Issues.Queryable - // .Select(i => i) - // .GroupBy(i => i.Key); + var projectKey = settings.GetProject(); - var issues = from i in jira.Issues.Queryable - select i; + // Adding logic for pagination + var itemsPerPage = 50; - // iterate and return each record - // foreach on results of JQL - foreach (var issue in issues) + var startAt = 0; + + while (true) { - var recordMap = new Dictionary(); + var issues = await jira.Issues.GetIssuesFromJqlAsync($"project = {projectKey} ORDER BY created DESC", itemsPerPage, startAt); + + if (issues.Count() == 0) + { + break; + } + + // iterate and return each record + // foreach on results of JQL + foreach (var issue in issues) + { + var recordMap = new Dictionary(); - // pull in all desired properties - recordMap["Key"] = issue.Key.Value; - recordMap["Project"] = issue.Project; - recordMap["Issuetype"] = issue.Type.Name; - recordMap["Description"] = issue.Description; - recordMap["Reporter"] = issue.ReporterUser.DisplayName; - recordMap["Created"] = issue.Created.Value; - recordMap["Status"] = issue.Status.Name; - recordMap["Resolution"] = issue.Resolution; - recordMap["Updated"] = issue.Updated.Value; + // pull in all desired properties + recordMap["Key"] = Convert.ToString(issue.Key.Value); + recordMap["Project"] = Convert.ToString(issue.Project); + recordMap["Issuetype"] = Convert.ToString(issue.Type.Name); + recordMap["Description"] = Convert.ToString(issue.Description); + recordMap["Reporter"] = Convert.ToString(issue.ReporterUser.DisplayName); + recordMap["Created"] = Convert.ToString(issue.Created.Value); + recordMap["Status"] = Convert.ToString(issue.Status.Name); + recordMap["Resolution"] = Convert.ToString(issue.Resolution); + recordMap["Updated"] = Convert.ToString(issue.Updated.Value); + yield return new Record + { + Action = Record.Types.Action.Upsert, + DataJson = JsonConvert.SerializeObject(recordMap) + }; + } - yield return new Record - { - Action = Record.Types.Action.Upsert, - DataJson = JsonConvert.SerializeObject(recordMap) - }; + startAt += itemsPerPage; } } + + public override async Task GetCountOfRecords(IApiClientFactory factory, Settings settings) + { + var jira = factory.CreateJiraClient(settings); + + var projectKey = settings.GetProject(); + + var issues = await jira.Issues.GetIssuesFromJqlAsync($"project = {projectKey} ORDER BY created DESC"); + + var total = issues.TotalItems; + + return new Count + { + Kind = Count.Types.Kind.Exact, + Value = (int) total + }; + } } public static readonly Dictionary IssuesEndpoints = new Dictionary diff --git a/PluginJira/DataContracts/ApplicationEndpointWrapper.cs b/PluginJira/DataContracts/ApplicationEndpointWrapper.cs new file mode 100644 index 0000000..d0b0235 --- /dev/null +++ b/PluginJira/DataContracts/ApplicationEndpointWrapper.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace PluginJira.DataContracts +{ + public class ApplicationEndpointWrapper + { + [JsonProperty("key")] + public string Key { get; set; } + + [JsonProperty("groups")] + public List Groups { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("defaultGroups")] + public List DefaultGroups { get; set; } + + [JsonProperty("selectedByDefault")] + public bool SelectedByDefault { get; set; } + + [JsonProperty("defined")] + public bool Defined { get; set; } + + [JsonProperty("numberOfSeats")] + public long NumberOfSeats { get; set; } + + [JsonProperty("remainingSeats")] + public long RemainingSeats { get; set; } + + [JsonProperty("userCount")] + public long UserCount { get; set; } + + [JsonProperty("userCountDescription")] + public string UserCountDescription { get; set; } + + [JsonProperty("hasUnlimitedSeats")] + public bool HasUnlimitedSeats { get; set; } + + [JsonProperty("platform")] + public bool Platform { get; set; } + + } +} \ No newline at end of file diff --git a/PluginJira/DataContracts/DataWrapper.cs b/PluginJira/DataContracts/DataWrapper.cs deleted file mode 100644 index 0908be0..0000000 --- a/PluginJira/DataContracts/DataWrapper.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Generic; - -namespace PluginJira.DataContracts -{ - public class DataWrapper - { - public long PageSize { get; set; } - public long PageNumber { get; set; } - public long TotalRecords { get; set; } - public long TotalPages { get; set; } - public List>? Items { get; set; } - } -} \ No newline at end of file diff --git a/PluginJira/Helper/Settings.cs b/PluginJira/Helper/Settings.cs index aab0868..12a272c 100644 --- a/PluginJira/Helper/Settings.cs +++ b/PluginJira/Helper/Settings.cs @@ -5,10 +5,10 @@ namespace PluginJira.Helper { public class Settings { - public string Username { get; set; } public string ApiKey { get; set; } public string Tenant { get; set; } + public string Project { get; set; } /// /// Validates the settings input object @@ -30,7 +30,12 @@ public void Validate() throw new Exception("The Api Key property must be set"); } - if (string.IsNullOrWhiteSpace(Tenant)) + if (string.IsNullOrWhiteSpace(Tenant)) + { + throw new Exception("The Tenant is not set properly"); + } + + if (string.IsNullOrWhiteSpace(Project)) { throw new Exception("The Tenant is not set properly"); } @@ -45,5 +50,10 @@ public string GetBaseUri() { return $"https://{Tenant}.atlassian.net/rest/api/2"; } + + public string GetProject() + { + return $"{Project}"; + } } } \ No newline at end of file diff --git a/PluginJiraTest/Plugin/PluginIntegrationTest.cs b/PluginJiraTest/Plugin/PluginIntegrationTest.cs index c6e411c..1f312ed 100644 --- a/PluginJiraTest/Plugin/PluginIntegrationTest.cs +++ b/PluginJiraTest/Plugin/PluginIntegrationTest.cs @@ -22,7 +22,8 @@ private Settings GetSettings() { ApiKey = "test123", Username = "test123", - Tenant = "test123" + Tenant = "test123", + Project="test" }; } @@ -296,10 +297,69 @@ public async Task ReadStreamTest() } // assert - Assert.Equal(20, records.Count); + Assert.Equal(254, records.Count); var record = JsonConvert.DeserializeObject>(records[0].DataJson); - // Assert.Equal("~", record["tilde"]); + + // cleanup + await channel.ShutdownAsync(); + await server.ShutdownAsync(); + } + + [Fact] + public async Task ReadStreamTest2() + { + // setup + Server server = new Server + { + Services = {Publisher.BindService(new PluginJira.Plugin.Plugin())}, + Ports = {new ServerPort("localhost", 0, ServerCredentials.Insecure)} + }; + server.Start(); + + var port = server.Ports.First().BoundPort; + + var channel = new Channel($"localhost:{port}", ChannelCredentials.Insecure); + var client = new Publisher.PublisherClient(channel); + + var schema = GetTestSchema("AllApplicationRoles"); + + var connectRequest = GetConnectSettings(); + + var schemaRequest = new DiscoverSchemasRequest + { + Mode = DiscoverSchemasRequest.Types.Mode.Refresh, + ToRefresh = {schema} + }; + + var request = new ReadRequest() + { + DataVersions = new DataVersions + { + JobId = "test" + }, + JobId = "test", + }; + + // act + client.Connect(connectRequest); + var schemasResponse = client.DiscoverSchemas(schemaRequest); + request.Schema = schemasResponse.Schemas[0]; + + var response = client.ReadStream(request); + var responseStream = response.ResponseStream; + var records = new List(); + + while (await responseStream.MoveNext()) + { + records.Add(responseStream.Current); + } + + // assert + Assert.Equal(1, records.Count); + + var record = JsonConvert.DeserializeObject>(records[0].DataJson); + // cleanup await channel.ShutdownAsync();