How to set a work item state while creating or updating work item in Azure Devops Rest API? - azure-devops-rest-api

I have been working to create an API which programatically creates/updates work item in Azure Devops. I have been able to create a work item and populate almost all fields. I have problem with setting the state.
When I am creating a POST request to Azure Devops rest api with any state name like "Active", "Closed", "Rejected", it throws a 400 Bad Request error.
I don't know if I am missing anything or if there something wrong with the way I am trying to set the value.
{
"op" : "add",
"path": "/fields/System.State",
"value"="Active",
}

I have found the solution to this problem and hence I am answering it here.
I was getting a 400 Bad Request error whenever I tried Creating an Item and Setting the state in the same call. I debugged the code and caught the exception. I found out that, there are some validation rules for some of the fields. State is one of them.
The rule for System.State field is, whenever a Work Item is created it takes its configured default value. ( In my case it was "Proposed", in your case it could be "New"). If you try altering the value at the time of work item creation, it throws a 400 Bad Request.
What should I do if I have to Create a Work Item with a specific State?
As of now, the solution that I have found out is to make two calls. One for Work Item Creation and another for Changing the state of the work item to desired state.
CreateWorkItem()
{
var result = await _client.Post(url, jsonData);
var result2 = await _client.Put(result.id, jsonData); // or maybe just the state
return Ok(result2);
}

Check the example here: Update a field
You have to use "value":"Active" in the request body.
[
{
"op" : "add",
"path": "/fields/System.State",
"value": "Active"
}
]

Related

Create team from group fails with exception

I created teams in Microsoft Teams (from groups as documented here) via the C# graph-api sdk without any problems - everything was working just fine.
But suddenly this is not working anymore. I will always get the following exception at the line return await graphServiceClient.Teams.Request().AddAsync(team);:
Message: Failed to execute Templates backend request
CreateTeamFromGroupWithTemplateRequest. Request Url:
https://teams.microsoft.com/fabric/emea/templates/api/groups/theGroupId/team,
Request Method: PUT,
And further:
Team Visibility can not be specified as it is inherited from the
group.
I know that the visibility property must not be set if creating the team from a group as it states in the Microsoft documentation:
The team that's created will always inherit from the group's display name, visibility, specialization, and members. Therefore, when making this call with the group#odata.bind property, the inclusion of team displayName, visibility, specialization, or members#odata.bind properties will return an error.
But the currently used code below shows that I am not setting any forbidden properties - and this code worked for the last few days, too:
private async Task<Team> CreateTeamFromGroup(string groupId)
{
var graphServiceClient = [...]
var groupResourceLink = $"https://graph.microsoft.com/v1.0/groups('{groupId}')";
var team = new Team
{
AdditionalData = new Dictionary<string, object>()
{
{ "template#odata.bind", "https://graph.microsoft.com/beta/teamsTemplates('standard')" },
{ "group#odata.bind", groupResourceLink }
},
Channels = new TeamChannelsCollectionPage
{
new Channel
{
DisplayName = "WhatEver"
}
}
};
return await graphServiceClient.Teams.Request().AddAsync(team);
}
Is anyone else experiencing this problem? Was there an API change? Was the teams backend changed? Anyone any ideas?
P.S.: I am using the latest NuGet-Package for Microsoft Graph - downgrading didn't help.
Update (with a not very satisfying work-around)
The error can be reproduced via the graph api explorer, too.
The POST command above issues a PUT command, that is described here. With this request, the team can be created.
The documentation and the graph api snippet for C# is out-dated, though. You have to add odatatype = null to the properties when using the sdk
Unfortunately it is not possible to add channels in the same step. If you specify the property 'channels' it will just be ignored.
Update (Detailed error message)
System.AggregateException: 'One or more errors occurred. (Code:
BadRequest Message: Failed to execute Templates backend request
CreateTeamFromGroupWithTemplateRequest. Request Url:
https://teams.microsoft.com/fabric/emea/templates/api/groups/theGroupId/team,
Request Method: PUT, Response Status Code: BadRequest,
ErrorMessage : {"errors":[{"message":"Team Visibility can not be
specified as it is inherited from the
group."}],"operationId":"639448e414ece64caee8f52839585bf7"} Inner
error: AdditionalData: date: 2020-11-24T10:21:22 request-id:
37a28cac-3ac5-4bd2-a061-daf44c442fac client-request-id:
37a28cac-3ac5-4bd2-a061-daf44c442fac ClientRequestId:
37a28cac-3ac5-4bd2-a061-daf44c442fac )'
Just tested this morning and I can say, that the "old way" by using the beta API to create a team with a template works again. Don't know, how many other ways exist to do these things, but here is our current request, that works now (again).
POST https://graph.microsoft.com/beta/teams
{
"displayName": "My Group Name",
"description": "Some description",
"template#odata.bind": "https://graph.microsoft.com/beta/teamsTemplates('educationClass')",
"owners#odata.bind": [
"https://graph.microsoft.com/beta/users('<someValidUserId>')"
]
}
I think this will be just an intermediate state and when the bugs are fixed, they will publish the new version again and this kind of creation will fail again, but if in this case the v1.0 documented way will work this wouldn't be a big problem. But being informed BEFORE there roll-out starts would be great.
This was a Microsoft issue/ bug and is currently being fixed as stated here.

setting 'replace_original' to false while responding to Slack message action request doesn't work

Background:
I am using the python slack API (slackclient) to build an iterative sequence of data-gathering actions in ephemeral messages.
The core of this works fine. After processing the incoming request that contains the user's interaction with a set of message buttons (or menus), I respond immediately with a JSON body, as described in the "Responding right away" section of the official slack docs.
The problem:
Every response replaces the preceding message+attachments. In many cases, this is what I want, but there are situations where I want to add a response rather than replace the previous message.
Per the slack docs,setting replace_original to false should do just that. But the following code, snipped from my handling of a simple button click (for example), replaces the original button (and the text message to which it was attached):
r = {
'response_type': 'ephemeral',
'text': 'foo',
'replace_original': 'false'
}
log.debug("Returning: {}".format(json.dumps(r)))
resp = Response(response=json.dumps(r),
mimetype="application/json",
status=200)
return resp
I have tried this with and without the delete_original and response_type fields, with no change.
In short, it appears that in this case the replace_original field isn't doing anything at all; behavior is always as if it were set to 'true'.
I feel like I must be missing something here - any help is greatly appreciated.
Simple solution here: the slack API is expecting a boolean, not a string. So 'replace_original': 'false' in the above snippet ends up as {"response_type": "ephemeral", "text": "foo", "replace_original": "false"} after the json.dumps() call, which is invalid.
Instead, setting 'replace_original': False becomes {"response_type": "ephemeral", "text": "foo", "replace_original": false}, which then has the expected behavior

$batch request resulting in error "Default changeset implementation allows only one operation"

I am making a worklist application using SAPUI5. The problem is that when I create an entry and then create another one right after that, I get the following error:
Default changeset implementation allows only one operation.
I checked the $batch header and I see that there is a MERGE and a POST, with the MERGE updating the previous entry for some reason. Can anyone shed some light? Could it be a backend error and not a UI5 error?
Creating the new entry:
_onMetadataLoaded: function() {
var oModel = this.getView().getModel();
var that = this;
// ...
oModel.read("/USERS_SET", {
success: function(oData) {
var oProperties = {
Qmnum: "0",
Otherstuff: "cool"
};
that._oContext = that._oView.getModel().createEntry("/ENTITYSET", {
properties: oProperties
});
that.getView().setBindingContext(that._oContext);
// ...
}
});
},
handleSavePress: function(oEvent) {
// ...
this.getView().getModel().submitChanges({
success: function(oData) {
// ...
},
error: function(oError) {
// ...
}
});
},
tl-dr: Apparently you must be using the SAP Gateway. If you do not need to process those requests in one transaction then send them in different changesets. If you do not need batch calls at all consider turning it off by supplying your model with "useBatch": false upon instantiation. However if you need to process the requests together in one transaction then you have to read the details below.
In order to understand the problem you have to understand how the gateway and the batch and changeset requests work.
Batch requests consist of multiple requests bundled together. The purpose is to open only one connection and group together relevant requests so that the overhead is minimalized. Changesets form smaller blocks inside batch requests, where modification requests can be bundled and processed together in order to ensure an all-or-nothing characteristic.
So on the gateway side: there are two relevant classes for your OData service, assuming that you have used the SAP Gateway Service Builder (SEGW transaction). There is one with the ending ...DPC and one with ...DPC_EXT. Don't touch the former, it will be always regenerated when you update your service in the service builder. The latter is the one that we will need in this example. You will have to redefine at least two methods:
/IWBEP/IF_MGW_APPL_SRV_RUNTIME~CHANGESET_BEGIN
/IWBEP/IF_MGW_APPL_SRV_RUNTIME~CHANGESET_PROCESS
By default the changeset_begin method will only allow changeset processing for changesets where the number of requests equals to one. This can be handled automatically that's why a limitation exists. If there were more requests one could not ensure their processing automatically as they could have a business dependency on each other.
So make sure to allow a bundled (deferred mode) processing of changesets under the desired conditions:
/IWBEP/IF_MGW_APPL_SRV_RUNTIME~CHANGESET_BEGIN: first call the super->/iwbep/if_mgw_appl_srv_runtime~changeset_begin method in a try catch block, then loop at it_operation_info to decide and narrow down processing only in selected cases and then allow cv_defer_mode only for the selected cases, otherwise throw a /iwbep/cx_mgw_tech_exception=>changeset_not_supported exception.
/IWBEP/IF_MGW_APPL_SRV_RUNTIME~CHANGESET_PROCESS: all requests will be available in the it_changeset_request. Make sure to fill the ct_changeset_response table with the responses.
METHOD /iwbep/if_mgw_appl_srv_runtime~changeset_process.
DATA:
lv_operation_counter TYPE i VALUE 0,
lr_context TYPE REF TO /iwbep/cl_mgw_request,
lr_entry_provider TYPE REF TO /iwbep/if_mgw_entry_provider,
lr_message_container TYPE REF TO /iwbep/if_message_container,
lr_entity_data TYPE REF TO data,
ls_context_details TYPE /iwbep/if_mgw_core_srv_runtime=>ty_s_mgw_request_context,
ls_changeset_response LIKE LINE OF ct_changeset_response.
FIELD-SYMBOLS:
<fs_ls_changeset_request> LIKE LINE OF it_changeset_request.
LOOP AT it_changeset_request ASSIGNING <fs_ls_changeset_request>.
lr_context ?= <fs_ls_changeset_request>-request_context.
lr_entry_provider = <fs_ls_changeset_request>-entry_provider.
lr_message_container = <fs_ls_changeset_request>-msg_container.
ls_context_details = lr_context->get_request_details( ).
CASE ls_context_details-target_entity.
WHEN 'SomeEntity'.
"Do the processing here
WHEN OTHERS.
ENDCASE.
ENDLOOP.
ENDMETHOD.
From the error I can tell you must be using SAP GW :-) This happens only for batch requests containing more than one create/delete/update calls and it's related to transaction security ("all or nothing"). What you have to do is redefining the corresponding GW method, I think it was CHANGESET_BEGIN. See https://archive.sap.com/discussions/thread/3562720 for some details (can't offer more for now...).

Jira issue status not getting updated

I am trying to update Jira issue fields through REST Api, I am able to update summary, description, priority, reporter fields but the status.
Here is the code I am trying to run:
string jSonContent = (#"
{
""fields"": {
""summary"": ""data"",
""description"": ""modified."",
""priority"": {""name"": ""val""},
""reporter"": {""name"": ""abcdef#gmail.com""},
""status"": {""name"": ""WORK IN PROGRESS""}
}
}").Replace("data", summ).Replace("modified.", desc).Replace("val", pri);
request.AddParameter("application/json", jSonContent, ParameterType.RequestBody);
var response = Execute(request);
You cannot change the status of an issue the way like that.
To determine what type of fields could be changed with a simple PUT request do a GET for metadata:
https://{your-jira-url}/rest/api/2/issue/{issueIdOrKey}/editmeta
This query in turn will provide you all the fields that you can modify. You won't find status field in the returned JSON object.
Back to your problem: How could be the status of an issue changed? In Jira you have a workflow that holds the possible transition between the states. In order to change the state you need to do a transition. (Exactly the same way as you would do it on UI.)
So first do a GET request like that:
https://{your-jira-url}/rest/api/2/issue/{issueIdOrKey}/transitions?expand=transitions.fields
This request will return all possible transitions of your issue's current state. Check which transition you want to perform and note it's ID (in my case the wished ID is 11). With this transition ID you can do a POST request with the JSON payload:
https://{your-jira-url}/rest/api/2/issue/{issueIdOrKey}/transitions
{
"transition": {
"id": "11"
}
}
One additional thing to note: If your transition isn't a simple one then you have to provide more data. I mean a simple transition here where you simply would click on a button on the UI and you wouldn't get an extra screen for the transition. (E.g. you can setup a transition like: you only could resolve an issue if you add a comment to it.) Fortunately, the previously returned transition list contains all the fields that could or that must be provided together with the transition ID.
You can find more information in official Jira documentation.

"Internal Error in key fixup - unable to locate entity" error but database updated as expected

I'm struggling to debug this error because, although it's consistently reported, the app's behavior is as intended. Would appreciate pointers as to what it means and how I could go about debugging its source.
Apologies for being vague but, since I'm getting the desired result, I'm unsure what other information to provide.
UPDATE
I've created a repro of this issue and attempted to focus the code on the problem. The error is thrown consistently even though the database is updated correctly. There's a single saveChanges in the code and it uses the save functionality from the dataservice.js in the Breeze Todo sample. SaveOptions.allowConcurrentSaves is false.
Entirely at a loss to explain it and have looked through my EF code to see whether I'm making an obvious mistake but can't see it. The bundle sent to the WebAPI SaveChanges method looks correct (correctly populated with IDs etc.) too.
https://github.com/DazWilkin/BreezeJS.ScoreIssue
UPDATE 6th February
The issue remains unresolved by Wade's helpful answer. Unfortunately, unless I can understand what it is I'm doing wrong or learn that this is a bug, I'm going to have to abandon the use of Breeze in this project and revert to crappy, plain old AJAX calls.
It would appear that the issue revolves around the server returning a zeroed GUID when saving changes. The method returns no errors. I would be thrilled to learn that this is a bug in my entity model but I'm doubtful.
Here's the failure:
breeze.debug.js: 11954
var ix = this._indexMap[tempValue];
if (ix === undefined) {
throw new Error("Internal Error in key fixup - unable to locate entity");
}
When the code reaches this point, the value of this._indexMap is correct and is:
{"bcb6e670-00fc-469d-8531-5767f40bf3c1":0}
BUT the value of tempValue (as returned from the Web API call by the server) is wrong:
00000000-0000-0000-0000-000000000000
The realValue is correct and is:
1093b975-7686-4621-8336-77c38ed36de0
Backing up the stack. Here are the results from the AJAX call, breeze.debug.js: 12574. See that the tempValue is zeroed on return from the server/WebAPI call. The realValue is correct. This is what the database contains. The row is added to the table without problem.
"KeyMappings": [
{
"$id": "4",
"$type": "Breeze.WebApi.KeyMapping, Breeze.WebApi",
"EntityTypeName": "...Score",
"TempValue": "51877f5b-811f-4260-bd5b-cf9965159597",
"RealValue": "92b73b8a-8b33-45cd-9822-ca7c0c5d5d9a"
},
{
"$id": "5",
"$type": "Breeze.WebApi.KeyMapping, Breeze.WebApi",
"EntityTypeName": "...PropertyValue",
"TempValue": "00000000-0000-0000-0000-000000000000",
"RealValue": "1093b975-7686-4621-8336-77c38ed36de0"
}
],
Verified against what's received serverside in saveBundle. NB the IDs of both entities received at the server have valid GUID IDs.
"entities": [
{
"ID": "51877f5b-811f-4260-bd5b-cf9965159597",
...
"entityAspect": {
"entityTypeName": "Score:...",
"entityState": "Added",
"originalValuesMap": {},
"autoGeneratedKey": {
"propertyName": "ID",
"autoGeneratedKeyType": "Identity"
}
}
},
{
"ID": "bcb6e670-00fc-469d-8531-5767f40bf3c1",
...
"entityAspect": {
"entityTypeName": "PropertyValue:...",
"entityState": "Added",
"originalValuesMap": {},
"autoGeneratedKey": {
"propertyName": "ID",
"autoGeneratedKeyType": "Identity"
}
}
}
],
Unsurprisingly, the values sent to the server by the AJAX call that are created in breeze.debug.js: 10494 saveBundleStringified are correct and the same as those received by the server (won't reproduce but I assure you they are).
And, from my code, when the saveChanges is called,
manager.getChanges().length == 2
manager.getChanges()[0].ID() == "51877f5b-811f-4260-bd5b-cf9965159597" (Score)
manager.getChanges()[1].ID() == "bcb6e670-00fc-469d-8531-5767f40bf3c1" (PropertyValue)
and, as expected, these match the (temp) values of the entities' IDs during saveChanges, received by the server...
What am I doing wrong?? If I had hair, I'd be tearing it out!
I've solved it.
My inconsistently applied (!) convention is to make the setters internal/private on code first types. I say inconsistently because, after feeling that I'd exhausted all possibilities, I discovered that the PropertyValue type, i.e. the one originating the error, had an internal set.
Having removed this and rebuilt the solution, the problem is solved!
So:
public Guid ID { get; internal set; }
Should be:
public Guid ID { get; set; }
Update Jan 27:
Based on your comments to Sergey's answer, you may have been trying to do something with the changed entities before the save operation completed.
Those entities remain in their changed state ... often with temporary primary and foreign keys ... until the server reports a successful save.
You probably shouldn't touch them until the save succeeds. As Sergey observes, you should locate your post-save processing in the save success callback.
return manager.saveChanges()
.then(saveSucceeded)
.fail(saveFailed);
You should not wrap the saveChanges call in a jQuery Deferred. That is a waste of time and complexity. The EntityManager.saveChanges method returns a promise that your caller can consume. The view models can add their own success and failure callbacks
dataservice.saveChanges()
.then(hooray)
.fail(sadTrombone);
Concurrent saves
I noticed in your code that you are guarding against illegal concurrent saves using the time-delay approach you found in the Todo sample.
That approach is really only suitable for the demo. It won't work at all for you if your view models need to perform some tasks when the save succeeds. It won't work because the dataservice can't return a promise to the view models with the time-delay approach.
If you need non-blocking saves, check out the "Save Queuing" plugin described in the "Concurrent Saves" topic under the "Cool Breezes" section.
Creating entities succinctly
While looking at your code, I couldn't help noticing that your entity factory methods in scoreissue.1.0.ts were a bit verbose. What you have written as:
export function Business(manager, o: IBusiness) {
var business = manager.metadataStore.getEntityType("Business").createEntity();
business.ID(breeze.core.getUuid());
business.Name(o.name);
manager.addEntity(business);
return business;
}
could be as simple as:
export function Business(manager, o: IBusiness) {
return manager.createEntity("Business", {
ID: breeze.core.getUuid(),
Name: o.name,
});
}
The EntityManager.createEntity shortcut is new since you wrote this code so don't feel bad about having missed it.
Original answer:
[Wrong direction. Preserved to make sense of DazWilkin comment that the problem is on the client.]
Where is this being generated? On the server? If so, you can subclass EFContextProvider and override SaveChangesCore. Call the base.SaveChangesCore and put a try/catch around it. Inspect the saveMap argument. The EFContextProvider is open source; I'd start digging here.
This error can occur with multiple simultaneous save requests pending at the same time both involving key generation. Does this error only occur during a save? If so try setting your SaveOptions.allowConcurrentSaves to false. If this causes a different error, (a concurrent save error) to occur then your problem definitely has to do with concurrent saves.
Hope this helps.
I have the same problem, and I've solved it with help of jQuery deferred object. My dataservice save method now looks as follows:
saveChanges = function () {
var def = $.Deferred();
if (manager.hasChanges()) {
manager.saveChanges()
.then(function () { def.resolve() })
.fail(function (error) {
handleSaveError(error);
def.reject();
});
} else {
logger.info("Nothing to save");
def.resolve();
};
return def.promise();
};
And I call it from my view models also using jQuery deferred object:
$.when(dataservice.saveChanges())
.done(function () {
...some actions
})

Resources