Error from PUT request to save new Project Property: "The property value can not be empty."

I’m trying to save a project property configuration. This property doesn’t exist yet so I’m assuming I make a PUT request to set it for the first time. I’m referencing the API docs https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-properties/#api-rest-api-3-project-projectidorkey-properties-propertykey-put.

However my connect app is logging an error:

Error saving project property:"{\"errors\":{},\"errorMessages\":[\"The property value can not be empty.\"],\"httpStatusCode\":{\"empty\":false,\"present\":true}}"

The request from the front-end to the back-end seems to be OK and the call is being made to the REST API, I’m just not sure what the issue could be – is there some specific way that I need to format the data property in the PUT request options? Or do I need to follow some other method to create the project property first?

Thanks!

routes/index.js

...
const doPUT = (httpClient, options) => {
  return new Promise((resolve, reject) => {
    httpClient.put({
      url: options.url,
      data: options.data,
      headers: {
        'Content-Type': 'application/json',
      },
    }, (error, response, body) => {
      if (error || response.statusCode < 200 || response.statusCode >= 300) reject (error || body);
      resolve({statusCode: response.statusCode, message: body});
    })
  })
}

const saveProjectProperty = async ({ configuration, httpClient, projectId }) => {
  const url = `/rest/api/3/project/${projectId}/properties/configuration`;

  try {
    const response = await doPUT(httpClient, { url, data: configuration });
    console.log('PUT request result:', response);
    return('Success');
  }
  catch (error) {
    console.log(error);
    console.log("Error saving project property:" + JSON.stringify(error));
  }
};

const saveConfiguration = async (req, res) => {
    const httpClient = addon.httpClient(req);

    try {
      await saveProjectProperty({ configuration: req.body.configuration, httpClient, projectId: req.body.projectId });

      res.json({ message: 'Configuration saved!' });
    } catch (error) {
      console.error('Error processing data:', error);
      res.status(500).json({ error: 'Internal Server Error' });
    }
  }

app.post('/api/saveConfig', [
      addon.authenticate(true),
      addon.authorizeJira({ project: ["ADMINISTER_PROJECTS"] })
    ], safeHandler(saveConfiguration));

...

my-page.hbs

...

<script>
...

export const saveConfiguration = ({ AP, configuration, projectId, token }) => {
  fetch('/api/saveConfig', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `JWT ${token}`
    },
    body: JSON.stringify({
      projectId,
      configuration
    }),
  })
    .then(response => response.json())
    .then(data => {
      AP.flag.create({ title: 'Saved', body: data.message, type: 'succes' });
    })
    .catch(error => console.error('Error fetching data:', error));
}

let token = '{{token}}';
AP.context.getToken((t) => {
  token = t;
});

const configuration = {
  key: 1,
};

configurationForm.addEventListener('submit', (e) => {
  e.preventDefault();
  saveConfiguration({ AP, configuration, projectId: '{{projectId}}', token });
});

...
</script>

...

Hello @DSmith

I think you need to re-read the documentation for the Set project properties endpoint.

There is no such path as:

/rest/api/3/project/${projectId}/properties/configuration

it would be:

/rest/api/3/project/${projectId}/properties/${propertyKey}

…unless you’re trying to set the Project Property as having the key “configuration”? It’s hard to tell, as you also use the word ‘configuration’ in your code as a variable too.

Also, it looks like you’re defining a request body that contains both a projectID and the variable called configuration, but that can’t be right as the Project’s ID doesn’t go into the body of the request, only the contents of what you want to store in the Project Property.

I can only say the endpoints are working exactly as expected for me. Here I create (PUT) a new Project Property with the key myTestProperty and I put some stuff in it:

Here I GET that Project Property and my stuff is in it:

I suggest you test your request with a tool like Postman first, so you at least know if it’s your request / method is valid, and therefore the fault is in your coding.

I have read and re-read the documentation. There’s no guidance on what ‘propertyKey’ is so I went with ‘configuration’ because that’s the name of the thing that I’m trying to save, and the same name as the thing that’s in my code, which makes sense to me.

You may need to re-read the code snippets I put in my question – only the configuration object is in the body of the request.

Postman works fine so it must be a code issue or something about how httpClient constructs the request – I couldn’t find any documentation… The code’s working for other APIs, just not project properties.

Hi @DSmith, couple of suggestions
a) Try setting json:true in the doPUT function, httpClient.put options object (e.g. with url, data). Or JSON.stringify your data first.
b) The property key is your choice, but suggest includeingyour app key / org name in it, as the property key names are in the global issue namespace, (e.g. so if any other app writes or reads a property key called “configuration” it will clobber yours. … /${projectId}/properties/myapp-conf )

Chris

1 Like

Thanks, I tried renaming the property and adding json: true in the put request options. Same error.

Figured it out thanks to @Chris_at_DigitalRose. Instead of json: true I used the json key to hold the object that I want to set for the property. So instead of key ‘body’ or ‘data’ it’s json:

const doPUT = (httpClient, options) => {
  return new Promise((resolve, reject) => {
    httpClient.put({
      url: options.url,
      json: options.data,   // <--------- CHANGED THIS FROM data: options.data
      headers: {
        'Content-Type': 'application/json',
      },
    }, (error, response, body) => {
      if (error || response.statusCode < 200 || response.statusCode >= 300) reject (error || body);
      resolve({statusCode: response.statusCode, message: body});
    })
  })
}

Quite frustrating – other than combing through the code in atlassian-connect-express, how could I have found this sooner? Is there any documentation anywhere?