Can't add Action to Rule even though app is added to project

I have created a custom app server in Flask and I was able to install it to a project by going through the installation flow. However whenever I try to add the Action to the Rule, I click on my app’s action and it sends a get request to the Action Metadata endpoint, but I get the message something went wrong try again later. I even get a 200 response from the endpoint I made in my Flask app. Any idea as to why this is occurring.

When you send your response, in addition to returning a 200 are you also sending the metadata as shown here?

1 Like

Yes I created a dict in python and using Flask’s jsonify feature I return that metadata in said format whenever the endpoint is hit

Hmm, OK. Are you sending your response pretty much instantly? I found that if I wait even a few seconds, Asana throws that error. They are not very patient at waiting for a reply! :wink:

You might also want to post the exact response you’re sending back, just in case I can spot any issues with it.

Resolved this with Sahil offline. Leaving a note here just in case others run into the same issue.

This is related to Asana’s App Components Security documentation about CORS: “Add cross-origin resource sharing (CORS) headers to responses

Instructions on how to add CORS for Python Flask App Server:

  1. pip install Flask-Cors
  2. Import Flask-CORS: from flask_cors import CORS
  3. Add CORS(app) after app = Flask(__name__)

Additionally for development use https.

Instructions on how to add https to Python Flask App Server (for development):

  1. In your terminal window run the following commands in the root directory of your Python Flask App Server (This step is the same as the “Enable HTTPS” step on the app-components-example-app README):

  2. openssl req -x509 -newkey rsa:2048 -keyout keytmp.pem -out cert.pem -days 365

  3. openssl rsa -in keytmp.pem -out key.pem

  4. If serving the application with python <APP_SERVER_NAME>.py command, add ssl_context=("cert.pem", "key.pem") to app.run. Otherwise, if the application is being served using the flask command use flask run --cert=cert.pem --key=key.pem

  5. After completing step 2, you should see something similar to the following in your command line Running on https://<HOST_NAME>:<PORT> (Press CTRL+C to quit)

  6. If you are on Google Chrome navigate to your app server https://<HOST_NAME>:<PORT>/ in the URL you should see a warning “Your connection is not private” type “thisisunsafe” on your keyboard. Close the tab after you have done this. (This is the same as the “If blocked by Chrome…” step on the app-components-example-app README)

  7. Remember to update Rule Actions "Run action URL " and “Form metadata URL” for your App in Asana’s developer console to use https://<HOST_NAME>:<PORT>/<YOUR_RULE_ACTION_RUN_ACTION_ENDPOINT> and https://<HOST_NAME>:<PORT>/<YOUR_RULE_ACTION_METADATA_ENDPOINT>

4 Likes

Thanks for posting this resolution, @John_Vu!

1 Like

Thanks for posting this information, @John_Vu. This really helped me out and I’ll pay it forward to others with what I’ve learned.

For those of you building your web app with .NET Core, here are the lines of code I had to include in my project to enable CORS.

In Program.cs:

builder.Services.AddCors(options => options.AddPolicy(name: "AsanaCorsPolicy",
    policy => policy.WithOrigins("https://app.asana.com").AllowAnyHeader()));

app.UseCors("AsanaCorsPolicy");

On the controller endpoint returning the form metadata:

[AllowAnonymous]
[EnableCors("AsanaCorsPolicy")]

Also, based on my trial and error diagnosis of another cause of the “something went wrong” error message, the properties of the action form metadata seem to need to be lowercase, but I’ve found that camelCase works, too. And I ignore null property values for good measure, but that doesn’t seem to be required.

Serializing from a form metadata class object using System.Text.Json:

JsonSerializerOptions options = new() {
        PropertyNamingPolicy= JsonNamingPolicy.CamelCase,
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
      };
return JsonSerializer.Serialize(ruleFormMetadata, options);

And here is my form metadata class for those interested.

using System.Text.Json.Serialization;

namespace MyNamespace {
  public class RuleFormMetadata {
    public string Template { get; set; } = "form_metadata_v0";
    public Metadata Metadata { get; set; } = new Metadata();
  }

  public class Metadata {
    public string? On_Submit_Callback { get; set; }
    public string? On_Change_Callback { get; set; }
    public Field[]? Fields { get; set; }
  }

  public class Field {
    [JsonConverter(typeof(JsonStringEnumConverter))]
    public Type Type { get; set; }
    public string? Id { get; set; }
    public string? Name { get; set; }
    public bool Is_Required { get; set; }
    public string? Typeahead_Url { get; set; }
    public string? Placeholder { get; set; }
    [JsonConverter(typeof(JsonStringEnumConverter))]
    public Width Width { get; set; } = Width.full;
    public string? Value { get; set; }
    public Option[]? Options { get; set; }
  }

  public class Option {
    public string? Id { get; set; }
    public string? Label { get; set; }
    public string? Icon_Url { get; set; }
  }

  public enum Type {
    typeahead,
    multi_line_text,
    single_line_text,
    dropdown,
    date,
    datetime,
    checkbox,
    radio_button
  }

  public enum Width {
    full
  }
}
2 Likes