dapplo / Dapplo.Jira

This is a simple REST based JIRA client, written for Greenshot, by using Dapplo.HttpExtension
MIT License
35 stars 14 forks source link

Custom drop down fields? #52

Open SamirKharchi opened 3 years ago

SamirKharchi commented 3 years ago

I am trying to create an issue with custom fields (drop downs!) but I always get a bad request response upon issue creation.

Custom fields I can set without problems but only string type fields. The two fields I would like to set are drop downs and nothing seems to work for them, the client call always returns a 400 bad request (Exception message: "Field 'Change Reason' cannot be set. It is not on the appropriate screen, or unknown.").

Here is how I try to set such a drop down field:

new Issue { Fields = new IssueFields { CustomFields = { {"customfield_10401", "Sonstiges"} } ...

I am not sure if it's fine to simply pass a string of the drop down item to select as the value. I also tried a single-entry anonymous array and id (e.g. 10305) instead of the value ("Sonstiges") but that didn't change anything. Is it possible to set a custom field that is a combobox?

Btw. the field is definetly available in the issue type (required even). And I can create the issue with a simple Jira CLI client without a problem. The field is also available when I use client.Server.GetFieldsAsync()

toertchn commented 3 years ago

We also had a lot of custom fields. What type is your CustomField? For customfieldtypes:select and customfieldtypes:cascadingselect I use the following code:

[DebuggerDisplay("{Value}")]
class SelectCFType
{
    [JsonProperty("value")]
    public string Value { get; set; }

    public SelectCFType() { }

    public SelectCFType(string value)
    {
        this.Value = value;
    }
}

[DebuggerDisplay("{Value} => {child?.Value}")]
class CascadingSelectCFType : SelectCFType
{
    public CascadingSelectCFType child;

    [JsonIgnore]
    public string ChildValue
    {
        get { return child?.Value; }
    }

    public CascadingSelectCFType() { }

    public CascadingSelectCFType(string value) : base(value) { }

    public CascadingSelectCFType(string value, string child)
    {
        this.Value = value;
        this.child = new CascadingSelectCFType(child);
    }
}

To get the values:

var issue = await Client.Issue.GetAsync("TICKET-1");
var category = JsonConvert.DeserializeObject<CascadingSelectCFType>(issue.Fields.CustomFields[OwnJiraFields.Category].ToString());
var grund = JsonConvert.DeserializeObject<SelectCFType>(issue.Fields.CustomFields[OwnJiraFields.Grund].ToString());

And the update part:

var updateIssue = new IssueEdit();
updateIssue.Fields = new Dapplo.Jira.Entities.IssueFields();
//updateIssue.Fields.Priority = new Priority { Name = "High" };
updateIssue.Fields.CustomFields.Add(OwnJiraFields.Menge, "1");
updateIssue.Fields.CustomFields.Add(OwnJiraFields.Category, new CascadingSelectCFType("Abteilung", "Extern"));
updateIssue.Fields.CustomFields.Add(OwnJiraFields.Grund, new SelectCFType("Beschwerde"));

await Client.Issue.EditAsync("TICKET-1", updateIssue);
Lakritzator commented 3 years ago

I'd love to hear how I can simplify that process @toertchn ?

What about a: issue.GetCustomField<ctype>(string fieldName) and issue.AddCustomField(string fieldName, object)

Your "get value" code might look like this:

var issue = await Client.Issue.GetAsync("TICKET-1");
var category = issue.GetCustomField<CascadingSelectCFType>(OwnJiraFields.Category);
var grund = issue.GetCustomField<SelectCFType>(OwnJiraFields.Grund);

And the update part:

var updateIssue = new IssueEdit();
updateIssue.AddCustomField(OwnJiraFields.Menge, "1");
updateIssue.AddCustomField(OwnJiraFields.Category, new CascadingSelectCFType("Abteilung", "Extern"));
updateIssue.AddCustomField(OwnJiraFields.Grund, new SelectCFType("Beschwerde"));

await Client.Issue.EditAsync("TICKET-1", updateIssue);

Other suggestions are also welcome.

Lakritzator commented 3 years ago

I've implemented what I suggested, it's in PR #63 you can see the code how it could work here: https://github.com/dapplo/Dapplo.Jira/blob/258ce655ab4ee4cf4ecc9838c43713eb45dca8bd/src/Dapplo.Jira.Tests/IssueTests.cs#L140

I'd love to get some feedback on this...

Lakritzator commented 3 years ago

Btw.. In the example code I use e.g. "customfield_10001", but this is the name I used to create the field, in the code it will figure out the read ID. I was also confused, this shows how complex the part of getting the right custom field IDs is...

toertchn commented 3 years ago

Thank you - I try to test it on my next free day (maybe 6.1.2021)

To get all fields with type and schema you can call: https://www.jiraserver.com/rest/api/2/field

There you can find the id, name, name of the field in clauses and the type.

All known implementations of the CustomFieldType are listet there: https://docs.atlassian.com/software/jira/docs/api/8.5.3/com/atlassian/jira/issue/customfields/CustomFieldType.html

Our Jira server hosts the whole Company. So we have nearly 400 different custom fields over all projects. This is also the reason why I don't need all this information in the results. This costs a lot of traffic.

The hardest part is testing all these different things.

Lakritzator commented 3 years ago

Thank you - I try to test it on my next free day (maybe 6.1.2021)

Ah yes, Heilige Drei Könige! Due to the epidemic I don't have many days, as I need them for my kid, so I'm glad we have at least one "Feiertag"

To get all fields with type and schema you can call: https://www.jiraserver.com/rest/api/2/field There you can find the id, name, name of the field in clauses and the type.

Yes, var fields = await Client.Server.GetFieldsAsync();

All known implementations of the CustomFieldType are listed there: https://docs.atlassian.com/software/jira/docs/api/8.5.3/com/atlassian/jira/issue/customfields/CustomFieldType.html

I think will add a FieldDomain for this, to have better field support (creating etc.).

Our Jira server hosts the whole Company. So we have nearly 400 different custom fields over all projects. This is also the reason why I don't need all this information in the results. This costs a lot of traffic.

Sounds very familiar, it's the same with the company I work for. Maybe I will also consider caching, or a way you can plug-in your own cache. There is also another way to get the fields for a project, might make sense to provide multiple ways to do this.

The hardest part is testing all these different things.

Yeah, in this case I already tested the code, as what I pointed to is the unit test for the field handling.

toertchn commented 3 years ago

The extensions AddCustomField & GetCustomField works as expected.

There exists an api for custom fields (api/2/customFields) but I'm just a little admin without the rights to access them.

So I found the api createmeta

https://jiraserver/rest/api/2/issue/createmeta?projectKeys=PROJECTKEY&expand=projects.issuetypes.fields Gets all fields, custom fields and allowedValues ... but it's marked as deprecated.

The new way seems to be: https://jiraserver/rest/api/2/issue/createmeta/PROJECTKEY/issuetypes To get all issuetypes for the project and later call https://jiraserver/rest/api/2/issue/createmeta/PROJECTKEY/issuetypes/ISUETYPEID to get the rest of the definition.

But this doesn't list me one custom field we use. Don't know why. I also can't check this but i can imagine that a custom field also needs to be assigned to a project to get listet there? Its correctly listed in the GetFieldsAsync() result.

Lakritzator commented 3 years ago

But this doesn't list me one custom field we use. Don't know why. I also can't check this but i can imagine that a custom field also needs to be assigned to a project to get listed there? Its correctly listed in the GetFieldsAsync() result.

That is actually valuable information, thanks

toertchn commented 3 years ago

Can you change the GetCustomField(this Issue issue, string customFieldName) to return null if the requested field ist null?

The question mark should do the fix: return issue.Fields.CustomFields[customFieldName]?.ToString();

Lakritzator commented 3 years ago

I did this but I've also change the code to include a TryGetCustomField, otherwise you don't know the difference between a null value and not having the field at all. Also I've added a HasCustomField.