├── src ├── templates │ ├── api_reference │ │ ├── partials │ │ │ ├── pre_description.ejs │ │ │ ├── resource_description.ejs │ │ │ ├── curl_example.ejs │ │ │ ├── static_site_header.ejs │ │ │ └── field_table_row.ejs │ │ ├── index.js │ │ └── resource.ejs │ ├── ruby │ │ ├── index.js │ │ └── resource.ejs │ ├── js │ │ ├── index.js │ │ └── resource.ejs │ ├── python │ │ ├── index.js │ │ └── resource.ejs │ ├── api_explorer │ │ ├── index.js │ │ └── resource.ejs │ ├── php │ │ ├── index.js │ │ └── resource.ejs │ └── java │ │ ├── index.js │ │ └── resource.ejs ├── resource.js ├── resources │ ├── job.yaml │ ├── project_membership.yaml │ ├── workspace_membership.yaml │ ├── portfolio_memberships.yaml │ ├── organization_export.yaml │ ├── custom_field_settings.yaml │ ├── user_task_list.yaml │ ├── team.yaml │ ├── event.yaml │ ├── user.yaml │ ├── project_status.yaml │ ├── attachment.yaml │ ├── tag.yaml │ ├── story.yaml │ ├── section.yaml │ ├── workspace.yaml │ ├── webhook.yaml │ ├── portfolio.yaml │ ├── custom_fields.yaml │ └── project.yaml └── helpers.js ├── .npmignore ├── .travis.yml ├── .gitignore ├── LICENSE ├── package.json ├── CODE_OF_CONDUCT.md ├── test ├── resource_spec.js └── resource_schema.json ├── README.md └── gulpfile.js /src/templates/api_reference/partials/pre_description.ejs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | docs 2 | examples 3 | coverage 4 | test 5 | .gitignore 6 | .jshintrc 7 | .travis.yml 8 | -------------------------------------------------------------------------------- /src/templates/ruby/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | resource: { 3 | template: 'resource.ejs', 4 | filename: function(resource, helpers) { 5 | return resource.name + '.rb'; 6 | } 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /src/templates/js/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | resource: { 3 | template: 'resource.ejs', 4 | filename: function(resource, helpers) { 5 | return helpers.plural(resource.name) + '.js'; 6 | } 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /src/templates/python/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | resource: { 3 | template: 'resource.ejs', 4 | filename: function(resource, helpers) { 5 | return helpers.plural(resource.name) + '.py'; 6 | } 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /src/templates/api_explorer/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | resource: { 3 | template: 'resource.ejs', 4 | filename: function(resource, helpers) { 5 | return helpers.plural(resource.name) + '.ts'; 6 | } 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /src/templates/php/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | resource: { 3 | template: 'resource.ejs', 4 | filename: function(resource, helpers) { 5 | return helpers.plural(helpers.classify(resource.name)) + 'Base.php'; 6 | } 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /src/templates/java/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | resource: { 3 | template: 'resource.ejs', 4 | filename: function(resource, helpers) { 5 | return helpers.plural(helpers.classify(resource.name)) + 'Base.java'; 6 | } 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /src/templates/api_reference/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | resource: { 3 | template: 'resource.ejs', 4 | filename: function(resource, helpers) { 5 | return helpers.plural(resource.name) + '.md'; 6 | } 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /src/templates/api_reference/partials/resource_description.ejs: -------------------------------------------------------------------------------- 1 | ### <%= thisType%> 2 | <%=partial(resource.name, "pre_description", {resource: resource})%> 3 | <%=removeLineBreaks(resource.comment)%> 4 | <%if (resource.notes){%><% _.forEach(resource.notes, function(note) {%> 5 | <%=removeLineBreaks(note)%> 6 | <%})};%> 7 | -------------------------------------------------------------------------------- /src/templates/api_reference/partials/curl_example.ejs: -------------------------------------------------------------------------------- 1 | <%_.forEach(curl, function(example) {%><%if (example.description) {%>**<%= example.description%>** 2 | 3 | <%}%> 4 | ~~~ 5 | # Request 6 | <%= example.request%> \ 7 | <%= example.url%><%if (example.dataForRequest.length > 0) {%> \<%}%> 8 | <%_.forEach(example.dataForRequest, function(data, index) { 9 | %><%= data%><%if (index < example.dataForRequest.length -1) {%> \<%}%> 10 | <%});%> 11 | # Response 12 | <%= example.responseStatus%> 13 | <%= indent(example.response, '', true)%> 14 | ~~~ 15 | <%});%> 16 | -------------------------------------------------------------------------------- /src/templates/api_explorer/resource.ejs: -------------------------------------------------------------------------------- 1 | /// 2 | <% 3 | // This won't be used by the API explorer, so no reason to clutter the resources. 4 | delete resource.templates; 5 | %> 6 | /** 7 | * This file is auto-generated by the `asana-api-meta` NodeJS package. 8 | * We try to keep the generated code pretty clean but there will be lint 9 | * errors that are just not worth fixing. 10 | */ 11 | /* tslint:disable:max-line-length */ 12 | /* tslint:disable:eofline */ 13 | var resource = <%= JSON.stringify(resource, null, 2) %>; 14 | export = resource; 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6' 4 | sudo: false 5 | after_success: 6 | if [[ ! -z "$TRAVIS_TAG" && "$TRAVIS_PULL_REQUEST" == "false" ]]; then 7 | echo "Deploying, issuing pull requests to api-meta-incoming"; 8 | git config --global user.email "git@asana.com"; 9 | git config --global user.name "Asana"; 10 | gulp deploy; 11 | else 12 | echo "Skipping deployment (no tag or was a pull request)"; 13 | fi 14 | env: 15 | global: 16 | - GULP_DEBUG=1 17 | - PROD=1 18 | # ASANA_GITHUB_TOKEN=... 19 | - secure: "AkS+8f6jqmjcC7eIJavvpx05vAX9pljuvBIycFeUsFkHtyXCvDwiVMTkthf9dybl/xsx5jI5qxz6MsdmbO/jDIVW7+69krlJzBQiiCoZpwTFYpSk9+n2pnn5peR7rJL5RHyK8YEcmVRuG974tCFqYd2pKJu+6UEHutp/kn9VYVc=" 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Deployed apps should consider commenting this line out: 24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 25 | node_modules 26 | 27 | # Documentation 28 | docs 29 | 30 | # Coverage 31 | coverage 32 | 33 | # Dist 34 | dist 35 | 36 | #IntelliJ 37 | .idea 38 | -------------------------------------------------------------------------------- /src/templates/api_reference/partials/static_site_header.ejs: -------------------------------------------------------------------------------- 1 | --- 2 | ##### WARNING ##### 3 | # 4 | # This file is auto generated upon deploy of https://github.com/Asana/asana-api-meta. To contribute please 5 | # familiarize yourself with the process as documented in the README.md found in that repository. 6 | # 7 | ##### WARNING ##### 8 | 9 | 10 | parent-title: API Reference 11 | title: <%= thisType %> 12 | description: "Get documentation, references, and support for working with Asana <%= thisType %>" 13 | _template: dev-page 14 | _layout: default 15 | 16 | 17 | # Sidebar nav 18 | side_nav: 19 | <% _.forEach(resource.action_classes, function(action_class) {%><%= ' -'%> 20 | name: <%= action_class.name%> 21 | url_notrans: <%= action_class.url%> 22 | <%});%> 23 | 24 | # Header 25 | logo_text: Developers 26 | 27 | header_search: true; 28 | --- 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/resource.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | var yaml = require('js-yaml'); 3 | var fs = require('fs'); 4 | 5 | var resourceDir = './src/resources'; 6 | 7 | function names() { 8 | var files = fs.readdirSync(resourceDir); 9 | return files.map(function(filename) { 10 | var match = /^(.*)\.yaml$/.exec(filename); 11 | return match ? match[1] : null; 12 | }).filter(function(name) { 13 | return name; 14 | }).sort(); 15 | } 16 | 17 | function load(name) { 18 | var content = fs.readFileSync(resourceDir + '/' + name + '.yaml', 'utf8'); 19 | content = content.replace(/\!include\s+(\S+)/g, function(match, filename) { 20 | var included = fs.readFileSync(resourceDir + '/' + filename, 'utf8'); 21 | // Strip document header 22 | return included.replace(/^---+/m, ''); 23 | }); 24 | return yaml.load(content); 25 | } 26 | 27 | exports.names = names; 28 | exports.load = load; 29 | 30 | -------------------------------------------------------------------------------- /src/templates/api_reference/partials/field_table_row.ejs: -------------------------------------------------------------------------------- 1 | <% 2 | // Reminder: line breaks are very significant in Markdown tables. 3 | // The whole line with pipe characters needs to render on one line. Feel free to add newlines 4 | // for clarity, but just be aware that they should always be _inside_ a JS code block of < % \n\n % > 5 | // (minus the spaces to avoid that from being parsed) like here in this comment. 6 | %>| <%= property.name%> | <% 7 | _.forEach(property.example_values, function(value) { 8 | %><%= value%> <% 9 | }); 10 | if (property.access) { 11 | %>**<%= property.access%>.** <% 12 | } 13 | %><%= removeLineBreaks(property.comment)%><% 14 | if (property.notes) { 15 | %>

**Note:** <% _.forEach(property.notes, function(note) {%><%= removeLineBreaks(note, "

")%><%}) 16 | };%>| 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Asana 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asana-api-meta", 3 | "version": "0.11.2", 4 | "description": "Metadata for Asana API for generating client libraries and documentation", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node_modules/.bin/gulp test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/Asana/asana-api-meta.git" 12 | }, 13 | "keywords": [ 14 | "asana", 15 | "api" 16 | ], 17 | "author": "Greg Slovacek", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/Asana/asana-api-meta/issues" 21 | }, 22 | "homepage": "https://github.com/Asana/asana-api-meta", 23 | "devDependencies": { 24 | "bluebird": "^2.9.34", 25 | "dateformat": "^1.0.11", 26 | "fs-extra": "^0.15.0", 27 | "github": "^0.2.4", 28 | "gulp": "^3.9.1", 29 | "gulp-bump": "^0.1.11", 30 | "gulp-git": "^2.7.0", 31 | "gulp-mocha": "^2.0.0", 32 | "gulp-rename": "^1.2.0", 33 | "gulp-tag-version": "^1.2.1", 34 | "gulp-template": "^2.0.0", 35 | "inflect": "^0.3.0", 36 | "js-yaml": "^3.13.1", 37 | "jsonschema": "^1.0.0", 38 | "lodash": "^4.17.13", 39 | "sync-to-github": "^0.1.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 | 5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. 6 | 7 | Examples of unacceptable behavior by participants include: 8 | 9 | * The use of sexualized language or imagery 10 | * Personal attacks 11 | * Trolling or insulting/derogatory comments 12 | * Public or private harassment 13 | * Publishing other's private information, such as physical or electronic addresses, without explicit permission 14 | * Other unethical or unprofessional conduct. 15 | 16 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. 17 | 18 | This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. 19 | 20 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 21 | 22 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) 23 | -------------------------------------------------------------------------------- /test/resource_spec.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var fs = require('fs'); 3 | var util = require('util'); 4 | 5 | var Validator = require('jsonschema').Validator; 6 | var resource = require('../src/resource'); 7 | 8 | describe('Resource', function() { 9 | 10 | describe('#names', function() { 11 | it('should return known resources in order', function() { 12 | assert.deepEqual(resource.names(), [ 13 | 'attachment', 'custom_field_settings', 'custom_fields', 'event', 'job', 14 | 'organization_export', 'portfolio', 'portfolio_memberships', 'project', 15 | 'project_membership', 'project_status', 'section', 'story', 'tag', 'task', 16 | 'team', 'user', 'user_task_list', 'webhook', 'workspace', 'workspace_membership' ]); 17 | }); 18 | }); 19 | 20 | describe('#load', function() { 21 | var validator = new Validator(); 22 | var schema = JSON.parse(fs.readFileSync('./test/resource_schema.json')); 23 | resource.names().forEach(function(name) { 24 | it('should load `' + name + '` conforming to resource schema', function() { 25 | var r = resource.load(name); 26 | var result = validator.validate(r, schema); 27 | var MAX_ERRORS = 10; 28 | if (result.errors.length > 0) { 29 | var lines = [ 30 | "Schema validation failed with " + result.errors.length + " errors" 31 | ]; 32 | result.errors.forEach(function(error, index) { 33 | if (index > MAX_ERRORS) { 34 | return; 35 | } else if (index === MAX_ERRORS) { 36 | lines.push(""); 37 | lines.push("Too many errors, not showing them all."); 38 | return; 39 | } 40 | lines.push(""); 41 | lines.push( 42 | "Error #" + (index + 1) + ": " + error.property + " " + error.message); 43 | lines.push(" on instance:"); 44 | lines.push(util.inspect(error.instance)); 45 | }); 46 | // Massage output to be more readable in mocha reporter 47 | assert(false, lines.join("\n").replace(/\n/g, "\n ")); 48 | } 49 | }); 50 | }); 51 | }); 52 | 53 | }); 54 | 55 | -------------------------------------------------------------------------------- /src/templates/php/resource.ejs: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class <%= plural(classify(resource.name)) + "Base" %> 9 | { 10 | /** 11 | * @param Asana/Client client The client instance 12 | */ 13 | public function __construct($client) 14 | { 15 | $this->client = $client; 16 | }<% _.forEach(resource.actions, function(action) { 17 | if (action.no_code) { 18 | return; 19 | } 20 | 21 | var name = snake(action.name); 22 | var method = action.method.toLowerCase(); 23 | if (action.collection) { 24 | method += "Collection"; 25 | } 26 | 27 | var extraParam = null; 28 | if (action.method === "GET") { 29 | extraParam = { 30 | name: 'params', 31 | type: 'Object', 32 | comment: 'Parameters for the request' 33 | }; 34 | } else if (action.method !== 'DELETE') { 35 | extraParam = { 36 | name: 'data', 37 | type: 'Object', 38 | comment: 'Data for the request' 39 | }; 40 | } 41 | var params = paramsForAction(action); 42 | var pathParamNames = params.pathParams.map(function(param) { return camel(snake(param.name), false); }); 43 | var explicitParams = params.pathParams.concat(params.explicitNonPathParams).concat(extraParam ? [extraParam] : []); 44 | var documentedParams = explicitParams.concat(params.optionParams); 45 | var phpPathParamNames = pathParamNames.map(function(p) { return "$" + p; }); 46 | %> 47 | 48 | /** 49 | <%= comment(action.comment, 4) %> 50 | *<% _.forEach(params.pathParams, function(param) { %> 51 | <%= comment('@param ' + param.name + ' ' + param.comment, 4) %><% }); %> 52 | * @return response 53 | */ 54 | public function <%= action.name %>(<%= phpPathParamNames.concat([""]).join(", ") %>$params = array(), $options = array()) 55 | {<% if (pathParamNames.length > 0) { %> 56 | $path = sprintf(<%= JSON.stringify(action.path) %>, <%= phpPathParamNames.join(", ") %>); 57 | return $this->client-><%= method %>($path, $params, $options); 58 | <% } else { %> 59 | return $this->client-><%= method %>(<%= JSON.stringify(action.path) %>, $params, $options); 60 | <% } %>}<% }); %> 61 | } 62 | -------------------------------------------------------------------------------- /src/templates/python/resource.ejs: -------------------------------------------------------------------------------- 1 | <% 2 | var thisType = classify(resource.name) 3 | 4 | function paramNameInComment(param) { 5 | return param.required ? param.name : ('[' + param.name + ']'); 6 | } 7 | %> 8 | class _<%= thisType %>: 9 | <%= '"""' + prefix(resource.comment, " ", true)%> 10 | """ 11 | 12 | def __init__(self, client=None): 13 | self.client = client 14 | <% _.forEach(resource.actions, function(action) { 15 | if (action.no_code) { 16 | return; 17 | } 18 | 19 | var name = snake(action.name); 20 | var method = action.method.toLowerCase(); 21 | if (action.collection) { 22 | method += "_collection"; 23 | } 24 | 25 | var extraParam = null; 26 | if (action.method === "GET") { 27 | extraParam = { 28 | name: 'params', 29 | type: 'Object', 30 | comment: 'Parameters for the request' 31 | }; 32 | } else if (action.method !== 'DELETE') { 33 | extraParam = { 34 | name: 'data', 35 | type: 'Object', 36 | comment: 'Data for the request' 37 | }; 38 | } 39 | var params = paramsForAction(action); 40 | var pathParamNames = params.pathParams.map(function(param) { return snake(param.name); }); 41 | var explicitParams = params.pathParams.concat(params.explicitNonPathParams).concat(extraParam ? [extraParam] : []); 42 | //var documentedParams = explicitParams.concat(params.optionParams); 43 | %> 44 | def <%= name %>(self, <%= pathParamNames.concat([""]).join(", ") %>params={}, **options): 45 | """<%= prefix(action.comment, " ", true) %> 46 | 47 | Parameters 48 | ----------<% _.forEach(explicitParams, function(param) { %> 49 | <%= prefix(paramNameInComment(param) + ' : {' + typeName(param.type) + "} " + 50 | param.comment, " ", true) %><% }); %><%_.forEach(params.optionParams, function(param) {%> 51 | - <%= prefix(paramNameInComment(param) + ' : {' + typeName(param.type) + "} " + 52 | param.comment, " ", true) %><%});%> 53 | """<% 54 | if (pathParamNames.length > 0) { %> 55 | path = <%= JSON.stringify(action.path) %> % (<%= pathParamNames.join(", ") %>) 56 | return self.client.<%= method %>(path, params, **options) 57 | <% } else { %> 58 | return self.client.<%= method %>(<%= JSON.stringify(action.path) %>, params, **options) 59 | <% } %><% 60 | }); 61 | %> 62 | -------------------------------------------------------------------------------- /src/resources/job.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | !include ../includes.yaml 3 | name: job 4 | comment: | 5 | A _job_ represents a process that handles asynchronous work. 6 | 7 | Jobs are created when an endpoint requests an action that will be handled asynchronously. 8 | Such as project or task duplication. 9 | 10 | notes: 11 | - | 12 | Only the creator of the duplication process can access the duplication status 13 | of the new object. 14 | properties: 15 | - name: gid 16 | <<: *PropType.Gid 17 | comment: | 18 | Globally unique ID of the job. 19 | 20 | - name: resource_type 21 | <<: *PropType.ResourceType 22 | comment: | 23 | The resource type of this resource. The value for this resource is always `job`. 24 | example_values: 25 | - '"job"' 26 | values: 27 | - name: job 28 | comment: A job resource type. 29 | 30 | - name: resource_subtype 31 | <<: *PropType.ResourceSubtype 32 | access: Read-only 33 | comment: | 34 | The type of job. 35 | example_values: 36 | - '"duplicate_project"' 37 | - '"duplicate_task"' 38 | values: 39 | - name: duplicate_project 40 | comment: A job that duplicates a project. 41 | - name: duplicate_task 42 | comment: A job that duplicates a task. 43 | 44 | - name: status 45 | <<: *PropType.JobStatus 46 | comment: 47 | The status of the job. 48 | example_values: 49 | - '"not_started"' 50 | - '"in_progress"' 51 | - '"succeeded"' 52 | - '"failed"' 53 | values: 54 | - name: not_started 55 | comment: The job has not started. 56 | - name: in_progress 57 | comment: The job is in progress. 58 | - name: succeeded 59 | comment: The job has completed. 60 | - name: failed 61 | comment: The job has not started. 62 | 63 | - name: new_project 64 | <<: *PropType.Project 65 | access: Read-only 66 | comment: | 67 | Contains the new project if the job created a new project. 68 | 69 | - name: new_task 70 | <<: *PropType.Task 71 | access: Read-only 72 | comment: | 73 | Contains the new task if the job created a new task. 74 | 75 | action_classes: 76 | - name: Get a job 77 | url: get 78 | 79 | actions: 80 | - name: findById 81 | class: get 82 | method: GET 83 | path: "/jobs/%s" 84 | params: 85 | - name: job 86 | <<: *Param.JobGid 87 | required: true 88 | comment: The job to get. 89 | comment: | 90 | Returns the complete job record for a single job. 91 | -------------------------------------------------------------------------------- /src/templates/java/resource.ejs: -------------------------------------------------------------------------------- 1 | <% 2 | var hasItemRequest = false, hasCollectionRequest = false; 3 | resource.actions.forEach(function(action) { 4 | hasItemRequest = hasItemRequest || (!action.no_code && !action.collection); 5 | hasCollectionRequest = hasCollectionRequest || (!action.no_code && action.collection); 6 | }); 7 | %>package com.asana.resources.gen; 8 | 9 | import com.asana.Client; 10 | import com.asana.resources.Resource; 11 | <% if (hasItemRequest || hasCollectionRequest) { %>import com.asana.models.<%= single(camel(resource.name)) %>; 12 | <% } 13 | %><% if (hasItemRequest) { %>import com.asana.requests.ItemRequest; 14 | <% } 15 | %><% if (hasCollectionRequest) { %>import com.asana.requests.CollectionRequest; 16 | <% } 17 | %> 18 | /** 19 | <%= comment(resource.comment) %> 20 | */ 21 | public class <%= plural(classify(resource.name)) + "Base" %> extends Resource { 22 | /** 23 | * @param client Parent client instance 24 | */ 25 | public <%= plural(classify(resource.name)) + "Base" %>(Client client) { 26 | super(client); 27 | }<% _.forEach(resource.actions, function(action) { 28 | if (action.no_code) { 29 | return; 30 | } 31 | 32 | var name = snake(action.name); 33 | var path = action.path; 34 | var method = action.method.toUpperCase(); 35 | var modelClass = single(camel(resource.name)); 36 | var returnClass; 37 | if (action.collection) { 38 | returnClass = "CollectionRequest<" + modelClass + ">"; 39 | } else { 40 | returnClass = "ItemRequest<" + modelClass + ">"; 41 | } 42 | 43 | var params = paramsForAction(action); 44 | var pathParamNames = params.pathParams.map(function(param) { return camel(snake(param.name), false); }); 45 | var pathParamNamesAndTypes = params.pathParams.map(function(param) { return typeName(param.type) + " " + camel(snake(param.name), false); }); 46 | %> 47 | 48 | /** 49 | <%= comment(action.comment, 4) %> 50 | *<% _.forEach(params.pathParams, function(param) { %> 51 | <%= comment('@param ' + camel(snake(param.name), false) + ' ' + param.comment, 4) %><% }); %> 52 | * @return Request object 53 | */ 54 | public <%= returnClass %> <%= action.name %>(<%= pathParamNamesAndTypes.join(", ") %>) { 55 | <% if (pathParamNames.length > 0) { %> 56 | String path = String.format(<%= JSON.stringify(path) %>, <%= pathParamNames.join(", ") %>); 57 | return new <%= returnClass %>(this, <%= modelClass %>.class, path, <%= JSON.stringify(method) %>); 58 | <% } else { %> 59 | return new <%= returnClass %>(this, <%= modelClass %>.class, <%= JSON.stringify(action.path) %>, <%= JSON.stringify(method) %>); 60 | <% } %>}<% }); %> 61 | } 62 | -------------------------------------------------------------------------------- /src/resources/project_membership.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # See `user.yaml` for more docs on these yaml files. 3 | !include ../includes.yaml 4 | name: project_membership 5 | comment: | 6 | With the introduction of "comment-only" projects in Asana, a user's membership 7 | in a project comes with associated permissions. These permissions (whether a 8 | user has full access to the project or comment-only access) are accessible 9 | through the project memberships endpoints described here. 10 | 11 | properties: 12 | 13 | - name: id 14 | <<: *PropType.Id 15 | comment: | 16 | Globally unique ID of the project membership. 17 | **Note: This field is under active migration to the [`gid` field](#field-gid)--please see our [blog post](/developers/documentation/getting-started/deprecations) for more information.** 18 | 19 | - name: gid 20 | <<: *PropType.Gid 21 | comment: | 22 | Globally unique ID of the project membership. 23 | 24 | - name: resource_type 25 | <<: *PropType.ResourceType 26 | comment: | 27 | The resource type of this resource. The value for this resource is always `project_membership`. 28 | example_values: 29 | - '"project_membership"' 30 | values: 31 | - name: project_membership 32 | comment: A project membership resource type. 33 | 34 | - name: user 35 | <<: *PropType.User 36 | example_values: 37 | - '{ id: 12345, gid: "12345", name: "Tim Bizarro" }' 38 | access: Read-only 39 | comment: | 40 | The user in the membership. 41 | 42 | - name: project 43 | <<: *PropType.Project 44 | access: Read-only 45 | comment: | 46 | [Opt In](https://asana.com/developers/documentation/getting-started/input-output-options). The project the user is a member of. 47 | 48 | - name: write_access 49 | <<: *PropType.WriteAccess 50 | access: Read-only 51 | comment: | 52 | Whether the user has full access to the project or has comment-only access. 53 | 54 | action_classes: 55 | - name: Get all memberships for a project 56 | url: get-many 57 | - name: Get a project membership 58 | url: get-single 59 | 60 | actions: 61 | 62 | - name: findByProject 63 | class: get-many 64 | method: GET 65 | path: "/projects/%s/project_memberships" 66 | params: 67 | - name: project 68 | <<: *Param.ProjectGid 69 | required: true 70 | comment: The project for which to fetch memberships. 71 | - name: user 72 | <<: *Param.User 73 | comment: If present, the user to filter the memberships to. 74 | collection: true 75 | comment: | 76 | Returns the compact project membership records for the project. 77 | 78 | 79 | - name: findById 80 | class: get-single 81 | method: GET 82 | path: "/project_memberships/%s" 83 | params: 84 | - name: project_membership 85 | <<: *Param.ProjectMembershipGid 86 | required: true 87 | comment: | 88 | Returns the project membership record. 89 | -------------------------------------------------------------------------------- /src/templates/api_reference/resource.ejs: -------------------------------------------------------------------------------- 1 | <% 2 | /* 3 | Reminder: line breaks are significant in Markdown in certain contexts. 4 | Feel free to add newlines for clarity, but just be aware that they should always be _inside_ 5 | a JS code block of < % //Commments, with\n\nwhitespace % > (minus the space between "< %", inserted to avoid parsing here) 6 | like here in this comment, which is between such a whitespace-safe code block. 7 | */ 8 | 9 | // We use the type name like "Tasks" a lot, so do that first from the resource name 10 | %><% 11 | var thisType = title(plural(resource.name)); 12 | %><% 13 | /* 14 | Render the static site header for page title, sidebar, etc... 15 | This is everything between the "---" blocks in the markdown file. 16 | */ 17 | %><%=partial("static_site_header", {thisType: thisType, resource:resource})%> 18 | 19 | <% 20 | /* 21 | Render the stuff above the property table with the resource name and overview 22 | description of what the resource represents. 23 | */ 24 | %><%=partial("resource_description", {thisType: thisType, resource: resource})%> 25 | 26 | <% //Render the table with the fields in it. 27 | %>#####<%= thisType%> have the following fields: {#fields} 28 | 29 | | Field | Description | 30 | |-------|-------------| 31 | <% _.forEach(resource.properties, function(property) { 32 | %><%=partial("field_table_row", {property: property})%> 33 | <%});%><% if (resource.extra_general_information) 34 | {%><%=resource.extra_general_information.text%><%} 35 | %> 36 | <%var examples = examplesForResource(thisType); 37 | _.forEach(resource.action_classes, function(action_class) { 38 | var matchingActions = resource.actions.filter(function(action) { 39 | return action.class == action_class.url; 40 | }); 41 | %> 42 | <%= '**[' + action_class.name.toUpperCase() + '](#' + action_class.url + ')**'%><%if (action_class.comment) {%><%= '\n' + removeLineBreaks(action_class.comment)%> 43 | <%}%> 44 | <%var curl_for_keys = curlExamplesForKeys(action_class.example_keys, examples);%> 45 | 46 | <% _.forEach(matchingActions, function(action) { 47 | var params = paramsForAction(action);%> 48 | 49 | ~~~ .nohighlight 50 | <%= action.method + ' ' + genericPath(action, params.pathParams)%> 51 | ~~~ 52 | 53 | <%= removeLineBreaks(action.comment)%> 54 | <%if (action.notes) {%><% _.forEach(action.notes, function(note) {%> 55 | <%= removeLineBreaks(note)%> 56 | <%})} 57 | %><%if (action.params) {%> 58 | | Parameter | Description | 59 | |---|---|<% _.forEach(action.params, function(param) {%> 60 | | <%if (_.find(params.pathParams, function(obj){return obj == param})){%><%= idIfyParamName(param.name) %><%} else {%><%= param.name %><%}%> | <%_.forEach(param.example_values, function(value) { 61 | %><%= value%> <%});%><%if (param.required) { 62 | %>**Required:** <%}%><%= removeLineBreaks(param.comment)%><%if (param.notes) { 63 | %>

**Note:** <% _.forEach(param.notes, function(note) {%><%= removeLineBreaks(note, "

")%><%})};%>|<% 64 | });}%> 65 | <%var curl = curlExamplesForAction(action, examples);%> 66 | <%= partial("curl_example", {curl: curl, indent: indent})%> 67 | <%if (action.footer) {%><%= removeLineBreaks(action.footer)%> 68 | <%}%><%});%><%= partial("curl_example", {curl: curl_for_keys, indent: indent})%> 69 | <%});%> 70 | -------------------------------------------------------------------------------- /src/resources/workspace_membership.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # See `user.yaml` for more docs on these yaml files. 3 | !include ../includes.yaml 4 | name: workspace_membership 5 | comment: | 6 | This object determines if a user is a member of a workspace. 7 | 8 | properties: 9 | 10 | - name: gid 11 | <<: *PropType.Gid 12 | comment: | 13 | Globally unique ID of the workspace membership. 14 | 15 | - name: resource_type 16 | <<: *PropType.ResourceType 17 | comment: | 18 | The resource type of this resource. The value for this resource is always `workspace_membership`. 19 | example_values: 20 | - '"workspace_membership"' 21 | values: 22 | - name: workspace_membership 23 | comment: A workspace membership resource type. 24 | 25 | - name: user 26 | <<: *PropType.User 27 | example_values: 28 | - '{ id: 12345, gid: "12345", name: "Tim Bizarro" }' 29 | access: Read-only 30 | comment: | 31 | The user in the membership. 32 | 33 | - name: workspace 34 | <<: *PropType.Workspace 35 | access: Read-only 36 | comment: | 37 | The workspace the user is a member of. 38 | 39 | - name: user_task_list 40 | <<: *PropType.UserTaskList 41 | access: Read-only 42 | comment: | 43 | The user's "My Tasks" in the workspace. 44 | 45 | - name: is_active 46 | <<: *PropType.Bool 47 | access: Read-only 48 | comment: | 49 | Reflects if this user still a member of the workspace. 50 | 51 | - name: is_admin 52 | <<: *PropType.Bool 53 | access: Read-only 54 | comment: | 55 | Reflects if this user is an admin of the workspace. 56 | 57 | - name: is_guest 58 | <<: *PropType.Bool 59 | access: Read-only 60 | comment: | 61 | Reflects if this user is a guest of the workspace. 62 | 63 | action_classes: 64 | - name: Get all memberships for a workspace 65 | url: get-many-workspace 66 | - name: Get all workspace memberships for a user 67 | url: get-many-user 68 | - name: Get a workspace membership 69 | url: get-single 70 | 71 | actions: 72 | - name: findByWorkspace 73 | class: get-many-workspace 74 | method: GET 75 | path: "/workspaces/%s/workspace_memberships" 76 | params: 77 | - name: workspace 78 | <<: *Param.WorkspaceGid 79 | required: true 80 | comment: The workspace for which to fetch memberships. 81 | - name: user 82 | <<: *Param.User 83 | comment: If present, the user to filter the memberships on. 84 | collection: true 85 | comment: | 86 | Returns the compact workspace membership records for the workspace. 87 | 88 | - name: findByUser 89 | class: get-many-user 90 | method: GET 91 | path: "/users/%s/workspace_memberships" 92 | params: 93 | - name: user 94 | <<: *Param.User 95 | required: true 96 | comment: If present, the user to filter the memberships on. 97 | collection: true 98 | comment: | 99 | Returns the compact workspace membership records for the user. 100 | 101 | - name: findById 102 | class: get-single 103 | method: GET 104 | path: "/workspace_memberships/%s" 105 | params: 106 | - name: workspace_membership 107 | <<: *Param.WorkspaceMembershipGid 108 | required: true 109 | comment: | 110 | Returns the workspace membership record. 111 | -------------------------------------------------------------------------------- /src/resources/portfolio_memberships.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # See `user.yaml` for more docs on these yaml files. 3 | !include ../includes.yaml 4 | name: portfolio_membership 5 | comment: | 6 | This object determines if a user is a member of a portfolio. 7 | 8 | properties: 9 | 10 | - name: id 11 | <<: *PropType.Id 12 | comment: | 13 | Globally unique ID of the portfolio membership. 14 | **Note: This field is under active migration to the [`gid` field](#field-gid)--please see our [blog post](/developers/documentation/getting-started/deprecations) for more information.** 15 | 16 | - name: gid 17 | <<: *PropType.Gid 18 | comment: | 19 | Globally unique ID of the portfolio membership. 20 | 21 | - name: resource_type 22 | <<: *PropType.ResourceType 23 | comment: | 24 | The resource type of this resource. The value for this resource is always `portfolio_membership`. 25 | example_values: 26 | - '"portfolio_membership"' 27 | values: 28 | - name: portfolio_membership 29 | comment: A portfolio membership resource type. 30 | 31 | - name: user 32 | <<: *PropType.User 33 | example_values: 34 | - '{ id: 12345, gid: "12345", name: "Tim Bizarro" }' 35 | access: Read-only 36 | comment: | 37 | The user in the membership. 38 | 39 | - name: portfolio 40 | <<: *PropType.Portfolio 41 | access: Read-only 42 | comment: | 43 | [Opt In](https://asana.com/developers/documentation/getting-started/input-output-options). The portfolio the user is a member of. 44 | 45 | action_classes: 46 | - name: Query for portfolio memberships 47 | url: query 48 | - name: Get all memberships for a portfolio 49 | url: get-many 50 | - name: Get a portfolio membership 51 | url: get-single 52 | 53 | actions: 54 | 55 | - name: findAll 56 | class: query 57 | method: GET 58 | path: "/portfolio_memberships" 59 | params: 60 | - name: portfolio 61 | <<: *Param.PortfolioGid 62 | comment: The portfolio for which to fetch memberships. 63 | - name: workspace 64 | <<: *Param.WorkspaceGid 65 | comment: The workspace for which to fetch memberships. 66 | - name: user 67 | <<: *Param.User 68 | comment: The user to filter the memberships on. 69 | collection: true 70 | comment: | 71 | Returns the compact portfolio membership records for the portfolio. You must 72 | specify `portfolio`, `portfolio` and `user`, or `workspace` and `user`. 73 | 74 | - name: findByPortfolio 75 | class: get-many 76 | method: GET 77 | path: "/portfolios/%s/portfolio_memberships" 78 | params: 79 | - name: portfolio 80 | <<: *Param.PortfolioGid 81 | required: true 82 | comment: The portfolio for which to fetch memberships. 83 | - name: user 84 | <<: *Param.User 85 | comment: If present, the user to filter the memberships on. 86 | collection: true 87 | comment: | 88 | Returns the compact portfolio membership records for the portfolio. 89 | 90 | 91 | - name: findById 92 | class: get-single 93 | method: GET 94 | path: "/portfolio_memberships/%s" 95 | params: 96 | - name: portfolio_membership 97 | <<: *Param.PortfolioMembershipGid 98 | required: true 99 | comment: | 100 | Returns the portfolio membership record. 101 | -------------------------------------------------------------------------------- /src/resources/organization_export.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | !include ../includes.yaml 3 | name: organization_export 4 | comment: | 5 | An _organization_export_ object represents a request to export the complete data of an Organization 6 | in JSON format. 7 | 8 | To export an Organization using this API: 9 | 10 | * Create an `organization_export` [request](#create) and store the id that is returned.\ 11 | * Request the `organization_export` every few minutes, until the `state` field contains 'finished'.\ 12 | * Download the file located at the URL in the `download_url` field. 13 | 14 | Exports can take a long time, from several minutes to a few hours for large Organizations. 15 | 16 | **Note:** These endpoints are only available to [Service Accounts](/guide/help/premium/service-accounts) 17 | of an [Enterprise](/enterprise) Organization. 18 | properties: 19 | 20 | - name: id 21 | <<: *PropType.Id 22 | comment: | 23 | Globally unique ID of the Organization export. 24 | **Note: This field is under active migration to the [`gid` field](#field-gid)--please see our [blog post](/developers/documentation/getting-started/deprecations) for more information.** 25 | 26 | - name: gid 27 | <<: *PropType.Gid 28 | comment: | 29 | Globally unique ID of the Organization export. 30 | 31 | - name: created_at 32 | <<: *PropType.DateTime 33 | access: Read-only 34 | comment: | 35 | The time at which this export was requested. 36 | 37 | - name: download_url 38 | type: String 39 | example_values: 40 | - "'https://asana-export.s3.amazonaws.com/export-4632784536274-20170127-43246.json.gz?AWSAccessKeyId=xxxxxxxx'" 41 | - "null" 42 | access: Read-only 43 | comment: | 44 | Download this URL to retreive the full export of the organization in JSON format. It will be 45 | compressed in a gzip (.gz) container. 46 | notes: 47 | - | 48 | May be null if the export is still in progress or failed. If present, this URL 49 | may only be valid for 1 hour from the time of retrieval. You should avoid 50 | persisting this URL somewhere and rather refresh on demand to ensure you 51 | do not keep stale URLs. 52 | 53 | - name: state 54 | type: String 55 | example_values: 56 | - "'pending'" 57 | - "'started'" 58 | - "'finished'" 59 | - "'error'" 60 | access: Read-only 61 | comment: | 62 | The current state of the export. 63 | 64 | - name: organization 65 | <<: *PropType.Workspace 66 | comment: | 67 | The Organization that is being exported. This can only be specified at create time. 68 | 69 | action_classes: 70 | - name: Get an Organization export 71 | url: get 72 | - name: Create a request to export an Organization 73 | url: create 74 | 75 | actions: 76 | 77 | # Create, Retrieve 78 | 79 | - name: findById 80 | class: get 81 | method: GET 82 | path: "/organization_exports/%s" 83 | params: 84 | - name: organization_export 85 | <<: *Param.Gid 86 | required: true 87 | comment: | 88 | Globally unique identifier for the Organization export. 89 | comment: | 90 | Returns details of a previously-requested Organization export. 91 | 92 | - name: create 93 | class: create 94 | method: POST 95 | path: "/organization_exports" 96 | params: 97 | - name: organization 98 | <<: *Param.WorkspaceGid 99 | required: true 100 | comment: | 101 | This method creates a request to export an Organization. Asana will complete the export at some 102 | point after you create the request. 103 | -------------------------------------------------------------------------------- /src/resources/custom_field_settings.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # See `user.yaml` for more docs on these yaml files. 3 | !include ../includes.yaml 4 | name: custom_field_settings 5 | comment: | 6 | 7 | Custom fields are applied to a particular project or portfolio with the 8 | Custom Field Settings resource. This resource both represents the 9 | many-to-many join of the Custom Field and Project or Portfolio as well as 10 | stores information that is relevant to that particular pairing; for instance, 11 | the `is_important` property determines some possible application-specific 12 | handling of that custom field and parent. 13 | 14 | properties: 15 | 16 | - name: id 17 | <<: *PropType.Id 18 | comment: | 19 | Globally unique ID of the custom field settings object. 20 | **Note: This field is under active migration to the [`gid` field](#field-gid)--please see our [blog post](/developers/documentation/getting-started/deprecations) for more information.** 21 | - name: gid 22 | <<: *PropType.Gid 23 | comment: | 24 | Globally unique ID of the custom field settings object. 25 | - name: resource_type 26 | <<: *PropType.ResourceType 27 | comment: | 28 | The resource type of this resource. The value for this resource is always `custom_field_setting` 29 | example_values: 30 | - '"custom_field_setting"' 31 | values: 32 | - name: custom_field_setting 33 | comment: A custom_field_setting resource type. 34 | - name: created_at 35 | <<: *PropType.DateTime 36 | access: Read-only 37 | comment: | 38 | The time at which this custom field setting was created. 39 | - name: is_important 40 | <<: *PropType.Bool 41 | access: Read-only 42 | comment: | 43 | `is_important` is used in the Asana web application to determine if this 44 | custom field is displayed in the task list (left pane) of a project. A 45 | project can have a maximum of 5 custom field settings marked as 46 | `is_important`. 47 | - name: parent 48 | <<: *PropType.CustomFieldSettingsParent 49 | comment: | 50 | The parent to which the custom field is applied. This can be a project 51 | or portfolio and indicates that the tasks or projects that the parent 52 | contains may be given custom field values for this custom field. 53 | - name: project 54 | <<: *PropType.Project 55 | comment: | 56 | **Deprecated: new integrations should prefer the `parent` field.** 57 | The id of the project that this custom field settings refers to. 58 | - name: custom_field 59 | access: Read-only 60 | <<: *PropType.CustomField 61 | comment: | 62 | The custom field that is applied to the `parent`. 63 | 64 | 65 | 66 | action_classes: 67 | - name: Query for custom field settings on a project 68 | url: query-project-settings 69 | - name: Query for custom field settings on a portfolio 70 | url: query-portfolio-settings 71 | 72 | actions: 73 | 74 | # Create, Retrieve, Update, Delete 75 | 76 | - name: findByProject 77 | class: query-project-settings 78 | method: GET 79 | path: "/projects/%s/custom_field_settings" 80 | params: 81 | - name: project 82 | <<: *Param.ProjectGid 83 | required: true 84 | comment: The ID of the project for which to list custom field settings 85 | collection: true 86 | comment: | 87 | Returns a list of all of the custom fields settings on a project. 88 | 89 | - name: findByPortfolio 90 | class: query-portfolio-settings 91 | method: GET 92 | path: "/portfolios/%s/custom_field_settings" 93 | params: 94 | - name: portfolio 95 | <<: *Param.PortfolioGid 96 | required: true 97 | comment: The ID of the portfolio for which to list custom field settings 98 | collection: true 99 | comment: | 100 | Returns a list of all of the custom fields settings on a portfolio. 101 | 102 | -------------------------------------------------------------------------------- /src/templates/js/resource.ejs: -------------------------------------------------------------------------------- 1 | <% 2 | var thisType = classify(resource.name); 3 | 4 | function paramNameInComment(param) { 5 | return param.required ? param.name : ('[' + param.name + ']'); 6 | } 7 | %> 8 | /** 9 | * This file is auto-generated by the `asana-api-meta` NodeJS package. 10 | * We try to keep the generated code pretty clean but there will be lint 11 | * errors that are just not worth fixing (like unused requires). 12 | * TODO: maybe we can just disable those specifically and keep this code 13 | * pretty lint-free too! 14 | */ 15 | /* jshint ignore:start */ 16 | var Resource = require('../resource'); 17 | var util = require('util'); 18 | var _ = require('lodash'); 19 | 20 | /** 21 | <%= comment(resource.comment) %> 22 | * @class 23 | * @param {Dispatcher} dispatcher The API dispatcher 24 | */ 25 | function <%= thisType %>(dispatcher) { 26 | Resource.call(this, dispatcher); 27 | } 28 | util.inherits(<%= thisType %>, Resource); 29 | 30 | <% _.forEach(resource.actions, function(action) { 31 | if (action.no_code) { 32 | return; 33 | } 34 | var isGet = action.method === 'GET'; 35 | var optionParams = []; 36 | var requestOptionsParamName = null; 37 | var dispatchName = 'dispatch' + cap(action.method); 38 | if (isGet) { 39 | optionParams.push({ 40 | name: 'params', 41 | type: 'Object', 42 | comment: 'Parameters for the request' 43 | }); 44 | requestOptionsParamName = 'params'; 45 | if (action.collection) { 46 | dispatchName = 'dispatchGetCollection'; 47 | } 48 | } else if (action.method !== 'DELETE') { 49 | optionParams.push({ 50 | name: 'data', 51 | type: 'Object', 52 | comment: 'Data for the request', 53 | required: true 54 | }); 55 | requestOptionsParamName = 'data'; 56 | } 57 | 58 | // Figure out how many params will be consumed by the path and put the 59 | // first N required params there - the rest go in options. 60 | var numPathParams = (action.path.match(/%/g) || []).length; 61 | var pathParams = []; 62 | var explicitNonPathParams = []; 63 | var optionChildParams = []; 64 | if (action.params) { 65 | action.params.forEach(function(param, index) { 66 | if (param.required && pathParams.length < numPathParams) { 67 | pathParams.push(param); 68 | } else if (param.explicit) { 69 | explicitNonPathParams.push(param); 70 | } else { 71 | optionChildParams.push( 72 | _.extend({}, param, { 73 | name: requestOptionsParamName + '.' + param.name 74 | })); 75 | } 76 | }); 77 | } 78 | 79 | // This includes the params that go on the path plus the request data param 80 | // and the dispatchOptions param. 81 | var explicitParams = pathParams 82 | .concat(explicitNonPathParams) 83 | .concat(optionParams); 84 | var allOrderedParams = explicitParams 85 | .concat(optionChildParams); 86 | 87 | // Add a dispatchOptions as the last param to every method. 88 | var dispatchOptionsParam = { 89 | name: 'dispatchOptions', 90 | type: 'Object', 91 | comment: 'Options, if any, to pass the dispatcher for the request' 92 | }; 93 | explicitParams.push(dispatchOptionsParam); 94 | allOrderedParams.push(dispatchOptionsParam); 95 | 96 | %> 97 | /** 98 | <%= comment(action.comment) %> 99 | <% _.forEach(allOrderedParams, function(param) { %><%= comment( 100 | '@param {' + typeName(param.type) + '} ' + paramNameInComment(param) + ' ' + param.comment, 2) %> 101 | <% }); %><%= comment( 102 | '@return {Promise} ' + 103 | ((isGet && !action.collection) ? 104 | 'The requested resource' : 105 | 'The response from the API'), 2) %> 106 | */ 107 | <%= thisType + '.prototype.' + action.name %> = function( 108 | <% _.forEach(explicitParams, function(param, i) { %> <%= camel(snake(param.name), false) %><% if (i !== explicitParams.length - 1) { %>, 109 | <% } }); %> 110 | ) { 111 | var path = util.format('<%= action.path %>'<% _.forEach(pathParams, function(param) { %>, <%= camel(snake(param.name), false) %><% }); %>); 112 | <% if (explicitNonPathParams.length > 0) { %> 113 | <%= requestOptionsParamName %> = _.extend({}, <%= requestOptionsParamName %> || {}, {<% _.forEach(explicitNonPathParams, function(npp, npp_index) { %> 114 | <%= npp.name %>: <%= npp.name %><%= npp_index !== explicitNonPathParams.length - 1 ? "," : "" %><% }); %> 115 | });<% } %> 116 | return this.<%= dispatchName %>(path<%= requestOptionsParamName ? (', ' + requestOptionsParamName) : ''%>, dispatchOptions); 117 | }; 118 | <% }); %> 119 | 120 | module.exports = <%= thisType %>; 121 | /* jshint ignore:end */ 122 | -------------------------------------------------------------------------------- /src/resources/user_task_list.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | !include ../includes.yaml 3 | name: user_task_list 4 | comment: | 5 | A _user task list_ represents the tasks assigned to a particular user. It provides API access to a user's "My Tasks" view in Asana. 6 | 7 | A user's "My Tasks" represent all of the tasks assigned to that user. It is 8 | visually divided into regions based on the task's 9 | [`assignee_status`](/developers/api-reference/tasks#field-assignee_status) 10 | for Asana users to triage their tasks based on when they can address them. 11 | When building an integration it's worth noting that tasks with due dates will 12 | automatically move through `assignee_status` states as their due dates 13 | approach; read up on [task 14 | auto-promotion](/guide/help/fundamentals/my-tasks#gl-auto-promote) for more 15 | infomation. 16 | 17 | 18 | properties: 19 | 20 | - name: id 21 | <<: *PropType.Id 22 | comment: | 23 | Globally unique ID of the user task list. 24 | **Note: This field is under active migration to the [`gid` field](#field-gid)--please see our [blog post](/developers/documentation/getting-started/deprecations) for more information.** 25 | 26 | - name: gid 27 | <<: *PropType.Gid 28 | comment: | 29 | Globally unique ID of the user task list. 30 | 31 | - name: resource_type 32 | <<: *PropType.ResourceType 33 | comment: | 34 | The resource type of this resource. The value for this resource is always `user_task_list`. 35 | example_values: 36 | - '"user_task_list"' 37 | values: 38 | - name: user_task_list 39 | comment: A user_task_list resource type. 40 | 41 | - name: name 42 | type: String 43 | example_values: 44 | - '"My Tasks"' 45 | comment: | 46 | The name of the user task list. 47 | access: Read-only 48 | 49 | 50 | - name: owner 51 | <<: *PropType.User 52 | comment: | 53 | The owner of the user task list, i.e. the person whose My Tasks is represented by this resource. 54 | access: Read-only 55 | 56 | - name: workspace 57 | <<: *PropType.Workspace 58 | comment: | 59 | The workspace in which the user task list is located. 60 | access: Read-only 61 | 62 | action_classes: 63 | - name: Get a user's user task list 64 | url: find-by-user 65 | - name: Get a user task list 66 | url: get 67 | - name: Get tasks in a user task list 68 | url: get-tasks 69 | 70 | actions: 71 | - name: findByUser 72 | class: find-by-user 73 | method: GET 74 | path: "/users/%s/user_task_list" 75 | params: 76 | - name: user 77 | <<: *Param.User 78 | required: true 79 | - name: workspace 80 | <<: *Param.WorkspaceGid 81 | required: true 82 | comment: | 83 | Returns the full record for the user task list for the given user 84 | 85 | - name: findById 86 | class: get 87 | method: GET 88 | path: "/user_task_lists/%s" 89 | params: 90 | - name: user_task_list 91 | <<: *Param.UserTaskListGid 92 | required: true 93 | comment: | 94 | Returns the full record for a user task list. 95 | 96 | 97 | - name: tasks 98 | class: get-tasks 99 | method: GET 100 | path: "/user_task_lists/%s/tasks" 101 | params: 102 | - name: user_task_list 103 | <<: *Param.UserTaskListGid 104 | required: true 105 | comment: The user task list in which to search for tasks. 106 | - name: completed_since 107 | <<: *Param.DateTime 108 | comment: | 109 | Only return tasks that are either incomplete or that have been 110 | completed since this time. 111 | collection: true 112 | comment: | 113 | Returns the compact list of tasks in a user's My Tasks list. The returned 114 | tasks will be in order within each assignee status group of `Inbox`, 115 | `Today`, and `Upcoming`. 116 | 117 | **Note:** tasks in `Later` have a different ordering in the Asana web app 118 | than the other assignee status groups; this endpoint will still return 119 | them in list order in `Later` (differently than they show up in Asana, 120 | but the same order as in Asana's mobile apps). 121 | 122 | **Note:** Access control is enforced for this endpoint as with all Asana 123 | API endpoints, meaning a user's private tasks will be filtered out if the 124 | API-authenticated user does not have access to them. 125 | 126 | **Note:** Both complete and incomplete tasks are returned by default 127 | unless they are filtered out (for example, setting `completed_since=now` 128 | will return only incomplete tasks, which is the default view for "My 129 | Tasks" in Asana.) 130 | -------------------------------------------------------------------------------- /src/resources/team.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | !include ../includes.yaml 3 | name: team 4 | comment: | 5 | A _team_ is used to group related projects and people together within an 6 | organization. Each project in an organization is associated with a team. 7 | properties: 8 | 9 | - name: id 10 | <<: *PropType.Id 11 | comment: | 12 | Globally unique ID of the team. 13 | **Note: This field is under active migration to the [`gid` field](#field-gid)--please see our [blog post](/developers/documentation/getting-started/deprecations) for more information.** 14 | 15 | - name: gid 16 | <<: *PropType.Gid 17 | comment: | 18 | Globally unique ID of the team. 19 | 20 | - name: resource_type 21 | <<: *PropType.ResourceType 22 | comment: | 23 | The resource type of this resource. The value for this resource is always `team`. 24 | example_values: 25 | - '"team"' 26 | values: 27 | - name: team 28 | comment: A team resource type. 29 | 30 | - name: name 31 | type: String 32 | example_values: 33 | - "'Engineering'" 34 | comment: | 35 | The name of the team. 36 | 37 | - name: description 38 | type: String 39 | example_values: 40 | - "'All developers should be members of this team.'" 41 | comment: | 42 | [Opt In](/developers/documentation/getting-started/input-output-options). The description of the team. 43 | 44 | - name: html_description 45 | <<: *PropType.HtmlText 46 | example_values: 47 | - "'<body><em>All</em> developers should be members of this team.</body>'" 48 | comment: | 49 | [Opt In](/developers/documentation/getting-started/input-output-options). The description of the team with formatting as HTML. 50 | 51 | - name: organization 52 | <<: *PropType.Workspace 53 | comment: | 54 | The organization the team belongs to. 55 | 56 | action_classes: 57 | - name: Get teams in organization 58 | url: get 59 | - name: Get team members 60 | url: users 61 | 62 | actions: 63 | 64 | # Create, Retrieve, Update, Delete 65 | 66 | - name: findById 67 | class: get 68 | method: GET 69 | path: "/teams/%s" 70 | params: 71 | - name: team 72 | <<: *Param.TeamId 73 | required: true 74 | comment: | 75 | Returns the full record for a single team. 76 | 77 | - name: findByOrganization 78 | class: get 79 | method: GET 80 | path: "/organizations/%s/teams" 81 | collection: true 82 | params: 83 | - name: organization 84 | <<: *Param.WorkspaceId 85 | required: true 86 | comment: | 87 | Returns the compact records for all teams in the organization visible to 88 | the authorized user. 89 | 90 | - name: findByUser 91 | class: get 92 | method: GET 93 | path: "/users/%s/teams" 94 | collection: true 95 | params: 96 | - name: user 97 | <<: *Param.User 98 | required: true 99 | - name: organization 100 | <<: *Param.WorkspaceId 101 | comment: The workspace or organization to filter teams on. 102 | comment: | 103 | Returns the compact records for all teams to which user is assigned. 104 | 105 | # Users 106 | 107 | - name: users 108 | class: users 109 | method: GET 110 | path: "/teams/%s/users" 111 | collection: true 112 | collection_cannot_paginate: true 113 | params: 114 | - name: team 115 | <<: *Param.TeamId 116 | required: true 117 | comment: | 118 | Returns the compact records for all users that are members of the team. 119 | 120 | - name: addUser 121 | class: users 122 | method: POST 123 | path: "/teams/%s/addUser" 124 | params: 125 | - name: team 126 | <<: *Param.TeamId 127 | required: true 128 | - name: user 129 | <<: *Param.User 130 | required: true 131 | comment: | 132 | The user making this call must be a member of the team in order to add others. 133 | The user to add must exist in the same organization as the team in order to be added. 134 | The user to add can be referenced by their globally unique user ID or their email address. 135 | Returns the full user record for the added user. 136 | 137 | - name: removeUser 138 | class: users 139 | method: POST 140 | path: "/teams/%s/removeUser" 141 | params: 142 | - name: team 143 | <<: *Param.TeamId 144 | required: true 145 | - name: user 146 | <<: *Param.User 147 | required: true 148 | comment: | 149 | The user to remove can be referenced by their globally unique user ID or their email address. 150 | Removes the user from the specified team. Returns an empty data record. 151 | -------------------------------------------------------------------------------- /test/resource_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Resource", 3 | "type": "object", 4 | "additionalProperties": false, 5 | "required": ["name", "comment", "properties", "actions"], 6 | "properties": { 7 | "templates": { "type": "array" }, 8 | "name": { "type": "string" }, 9 | "comment": { "type": "string" }, 10 | "notes": { 11 | "type": "array", 12 | "items": { "type": "string" } 13 | }, 14 | "properties": { 15 | "type": "array", 16 | "items": { 17 | "type": "object", 18 | "additionalProperties": false, 19 | "required": ["name", "type", "comment", "example_values"], 20 | "properties": { 21 | "name": { "type": "string" }, 22 | "type": { "type": "string" }, 23 | "comment": { "type": "string" }, 24 | "notes": { 25 | "type": "array", 26 | "items": { "type": "string" } 27 | }, 28 | "example_values": { 29 | "type": "array", 30 | "items": { "type": "string" }, 31 | "length": { "min": 1 } 32 | }, 33 | "access": { "type": "string" }, 34 | "values": { 35 | "type": "array", 36 | "items": { 37 | "type": "object", 38 | "required": ["name", "comment"], 39 | "additionalProperties": false, 40 | "properties": { 41 | "name": { "type": "string" }, 42 | "comment": { "type": "string" }, 43 | "notes": { 44 | "type": "array", 45 | "items": { "type": "string" } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | } 52 | }, 53 | "extra_general_information": { 54 | "type": "object", 55 | "items": { 56 | "type": "object", 57 | "properties": { 58 | "text": { "type": "string" } 59 | } 60 | } 61 | }, 62 | "action_classes": { 63 | "type": "array", 64 | "items": { 65 | "type": "object", 66 | "required": ["name", "url"], 67 | "additionalProperties": false, 68 | "properties": { 69 | "name": {"type": "string"}, 70 | "url": {"type": "string"}, 71 | "comment": {"type": "string"}, 72 | "example_keys": {"type": "array"} 73 | } 74 | } 75 | }, 76 | "actions": { 77 | "type": "array", 78 | "items": { 79 | "type": "object", 80 | "required": ["name", "method", "path", "comment"], 81 | "additionalProperties": false, 82 | "properties": { 83 | "name": { "type": "string" }, 84 | "class": { "type": "string" }, 85 | "method": { "type": "string" }, 86 | "path": { "type": "string" }, 87 | "comment": { "type": "string" }, 88 | "notes": { 89 | "type": "array", 90 | "items": { "type": "string" } 91 | }, 92 | "footer": { "type": "string"}, 93 | "no_code": { "type": "boolean" }, 94 | "collection": { "type": "boolean" }, 95 | "collection_cannot_paginate": { "type": "boolean" }, 96 | "params": { 97 | "type": "array", 98 | "items": { 99 | "type": "object", 100 | "required": ["name", "type", "comment"], 101 | "additionalProperties": false, 102 | "properties": { 103 | "name": { "type": "string" }, 104 | "type": { "type": "string" }, 105 | "required": { "type": "boolean" }, 106 | "explicit": { "type": "boolean" }, 107 | "comment": { "type": "string" }, 108 | "notes": { 109 | "type": "array", 110 | "items": { "type": "string" } 111 | }, 112 | "example_values": { 113 | "type": "array", 114 | "items": { "type": "string" }, 115 | "length": { "min": 1 } 116 | }, 117 | "values": { 118 | "type": "array", 119 | "items": { 120 | "type": "object", 121 | "required": ["name", "comment"], 122 | "additionalProperties": false, 123 | "properties": { 124 | "name": { "type": "string" }, 125 | "comment": { "type": "string" }, 126 | "notes": { 127 | "type": "array", 128 | "items": { "type": "string" } 129 | } 130 | } 131 | } 132 | } 133 | } 134 | } 135 | } 136 | } 137 | } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/resources/event.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | !include ../includes.yaml 3 | name: event 4 | comment: | 5 | An _event_ is an object representing a change to a resource that was observed 6 | by an event subscription. 7 | 8 | In general, requesting events on a resource is faster and subject to higher 9 | rate limits than requesting the resource itself. Additionally, change events 10 | bubble up - listening to events on a project would include when stories are 11 | added to tasks in the project, even on subtasks. 12 | 13 | Establish an initial sync token by making a request with no sync token. 14 | The response will be a `412` error - the same as if the sync token had 15 | expired. 16 | 17 | Subsequent requests should always provide the sync token from the immediately 18 | preceding call. 19 | 20 | Sync tokens may not be valid if you attempt to go 'backward' in the history 21 | by requesting previous tokens, though re-requesting the current sync token 22 | is generally safe, and will always return the same results. 23 | 24 | When you receive a `412 Precondition Failed` error, it means that the 25 | sync token is either invalid or expired. If you are attempting to keep a set 26 | of data in sync, this signals you may need to re-crawl the data. 27 | 28 | Sync tokens always expire after 24 hours, but may expire sooner, depending on 29 | load on the service. 30 | properties: 31 | 32 | - name: user 33 | <<: *PropType.User 34 | access: Read-only 35 | comment: | 36 | The user who triggered the event. 37 | notes: 38 | - | 39 | The event may be triggered by a different user than the subscriber. For 40 | example, if user A subscribes to a task and user B modified it, the 41 | event’s user will be user B. Note: Some events are generated by the 42 | system, and will have `null` as the user. API consumers should make sure 43 | to handle this case. 44 | 45 | - name: resource 46 | <<: *PropType.Task 47 | comment: | 48 | The resource the event occurred on. 49 | notes: 50 | - | 51 | The resource that triggered the event may be different from the one that 52 | the events were requested for. For example, a subscription to a project 53 | will contain events for tasks contained within the project. 54 | 55 | - name: type 56 | <<: *PropType.Type 57 | values: 58 | - name: task 59 | comment: A task. 60 | - name: project 61 | comment: A project. 62 | - name: story 63 | comment: A story. 64 | access: Read-only 65 | comment: | 66 | **Deprecated: Refer to the resource_type of the resource.** The type of the resource that generated the event. 67 | notes: 68 | - | 69 | Currently, only tasks, projects and stories generate events. 70 | 71 | - name: action 72 | <<: *PropType.Action 73 | comment: | 74 | The type of action taken that triggered the event. 75 | 76 | - name: parent 77 | <<: *PropType.Project 78 | comment: | 79 | For added/removed events, the parent that resource was added to or 80 | removed from. `null` for other event types. 81 | 82 | - name: created_at 83 | <<: *PropType.DateTime 84 | access: Read-only 85 | comment: | 86 | The timestamp when the event occurred. 87 | 88 | action_classes: 89 | - name: Get events on resource 90 | url: get-all 91 | example_keys: ["get-event-on-project"] 92 | 93 | actions: 94 | 95 | - name: get 96 | class: get-all 97 | method: GET 98 | path: "/events" 99 | collection: true 100 | # Calling get collection isn't quite right since it will add limit= 101 | no_code: true 102 | params: 103 | - name: resource 104 | <<: *Param.Gid 105 | required: true 106 | explicit: true 107 | comment: | 108 | A resource ID to subscribe to. The resource can be a task or project. 109 | - name: sync 110 | type: String 111 | explicit: true 112 | example_values: 113 | - "'de4774f6915eae04714ca93bb2f5ee81'" 114 | comment: | 115 | A sync token received from the last request, or none on first sync. 116 | Events will be returned from the point in time that the sync token 117 | was generated. 118 | notes: 119 | - | 120 | On your first request, omit the sync token. The response will be the 121 | same as for an expired sync token, and will include a new valid 122 | sync token. 123 | - | 124 | If the sync token is too old (which may happen from time to time) 125 | the API will return a `412 Precondition Failed` error, and include 126 | a fresh `sync` token in the response. 127 | comment: | 128 | Returns the full record for all events that have occurred since the 129 | sync token was created. 130 | notes: 131 | - | 132 | A GET request to the endpoint `/[path_to_resource]/events` can be made 133 | in lieu of including the resource ID in the data for the request. 134 | -------------------------------------------------------------------------------- /src/resources/user.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Yaml file for the User resource. 3 | # Schema for all resources exists in `resource_schema.json` and is validated 4 | # as part of tests. 5 | # All resource yaml files must include the includes. 6 | # We've extended our yaml loader to do a quick-and-dirty parse of these 7 | # includes, so that our files can remain syntactically still yaml even if 8 | # semantically they only work with includes. 9 | !include ../includes.yaml 10 | name: user 11 | comment: | 12 | A _user_ object represents an account in Asana that can be given access to 13 | various workspaces, projects, and tasks. 14 | 15 | Like other objects in the system, users are referred to by numerical IDs. 16 | However, the special string identifier `me` can be used anywhere 17 | a user ID is accepted, to refer to the current authenticated user. 18 | 19 | properties: 20 | 21 | - name: id 22 | <<: *PropType.Id 23 | comment: | 24 | Globally unique ID of the user. 25 | **Note: This field is under active migration to the [`gid` field](#field-gid)--please see our [blog post](/developers/documentation/getting-started/deprecations) for more information.** 26 | 27 | - name: gid 28 | <<: *PropType.Gid 29 | comment: | 30 | Globally unique ID of the user. 31 | 32 | - name: resource_type 33 | <<: *PropType.ResourceType 34 | comment: | 35 | The resource type of this resource. The value for this resource is always `user`. 36 | example_values: 37 | - '"user"' 38 | values: 39 | - name: user 40 | comment: A user resource type. 41 | 42 | - name: name 43 | type: String 44 | example_values: 45 | - "'Greg Sanchez'" 46 | access: Read-only except when same user as requester 47 | comment: | 48 | The user's name. 49 | 50 | - name: email 51 | <<: *PropType.Email 52 | comment: | 53 | The user's email address. 54 | 55 | - name: photo 56 | <<: *PropType.Photo 57 | comment: | 58 | A map of the user's profile photo in various sizes, or `null` if no photo 59 | is set. Sizes provided are 21, 27, 36, 60, and 128. Images are in 60 | PNG format. 61 | 62 | # Action class refers to a general category the action falls under. These are 63 | # referenced in the actions themselves by their url because we may want to change 64 | # the exact wording of the class names in the future. 65 | action_classes: 66 | - name: Get a single user 67 | url: get-single 68 | - name: Get all users 69 | url: get-all 70 | - name: Get a user's favorites 71 | url: get-favorites 72 | 73 | actions: 74 | 75 | - name: me 76 | class: get-single 77 | method: GET 78 | path: "/users/me" 79 | comment: | 80 | Returns the full user record for the currently authenticated user. 81 | 82 | - name: findById 83 | class: get-single 84 | method: GET 85 | path: "/users/%s" 86 | params: 87 | - name: user 88 | <<: *Param.User 89 | required: true 90 | comment: | 91 | Returns the full user record for the single user with the provided ID. 92 | 93 | - name: getUserFavorites 94 | class: get-favorites 95 | method: GET 96 | path: "/users/%s/favorites" 97 | params: 98 | - name: user 99 | <<: *Param.User 100 | required: true 101 | - name: workspace 102 | <<: *Param.WorkspaceId 103 | required: true 104 | comment: The workspace in which to get favorites. 105 | - name: resource_type 106 | type: Enum 107 | example_values: 108 | - user 109 | values: 110 | - name: portfolio 111 | comment: A portfolio. 112 | - name: project 113 | comment: A project. 114 | - name: tag 115 | comment: A tag. 116 | - name: task 117 | comment: A task. 118 | - name: user 119 | comment: A user. 120 | required: true 121 | comment: The resource type of favorites to be returned. 122 | comment: | 123 | Returns all of a user's favorites in the given workspace, of the given type. 124 | Results are given in order (The same order as Asana's sidebar). 125 | 126 | - name: findByWorkspace 127 | class: get-all 128 | method: GET 129 | path: "/workspaces/%s/users" 130 | collection: true 131 | collection_cannot_paginate: true 132 | params: 133 | - name: workspace 134 | <<: *Param.WorkspaceId 135 | required: true 136 | comment: The workspace in which to get users. 137 | comment: | 138 | Returns the user records for all users in the specified workspace or 139 | organization. 140 | notes: 141 | - | 142 | Results are sorted alphabetically by user `name`s. 143 | 144 | - name: findAll 145 | class: get-all 146 | method: GET 147 | path: "/users" 148 | collection: true 149 | params: 150 | - name: workspace 151 | <<: *Param.WorkspaceId 152 | comment: The workspace or organization to filter users on. 153 | comment: | 154 | Returns the user records for all users in all workspaces and organizations 155 | accessible to the authenticated user. Accepts an optional workspace ID 156 | parameter. 157 | notes: 158 | - | 159 | Results are sorted by user ID. 160 | -------------------------------------------------------------------------------- /src/resources/project_status.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # See `user.yaml` for more docs on these yaml files. 3 | !include ../includes.yaml 4 | name: project_status 5 | comment: | 6 | A _project status_ is an update on the progress of a particular project, and is sent out to all project 7 | followers when created. These updates include both text describing the update and a color code intended to 8 | represent the overall state of the project: "green" for projects that are on track, "yellow" for projects 9 | at risk, and "red" for projects that are behind. 10 | 11 | Project statuses can be created and deleted, but not modified. 12 | 13 | properties: 14 | 15 | - name: id 16 | <<: *PropType.Id 17 | comment: | 18 | Globally unique ID of the project status update. 19 | **Note: This field is under active migration to the [`gid` field](#field-gid)--please see our [blog post](/developers/documentation/getting-started/deprecations) for more information.** 20 | 21 | - name: gid 22 | <<: *PropType.Gid 23 | comment: | 24 | Globally unique ID of the project status update. 25 | 26 | - name: resource_type 27 | <<: *PropType.ResourceType 28 | comment: | 29 | The resource type of this resource. The value for this resource is always `project_status`. 30 | example_values: 31 | - '"project_status"' 32 | values: 33 | - name: project_status 34 | comment: A project status resource type. 35 | 36 | - name: title 37 | type: String 38 | access: Read-only 39 | comment: | 40 | The title of the project status update. 41 | example_values: 42 | - "'Status Update - Jun 15'" 43 | 44 | - name: text 45 | type: String 46 | access: Read-only 47 | comment: | 48 | The text content of the status update. 49 | example_values: 50 | - "'The project is moving forward according to plan...'" 51 | 52 | - name: html_text 53 | <<: *PropType.HtmlText 54 | access: Read-only 55 | comment: | 56 | [Opt In](https://asana.com/developers/documentation/getting-started/input-output-options). The text content of the status update with formatting as HTML. 57 | example_values: 58 | - "'<body>The project <strong>is</strong> moving forward according to plan...</body>'" 59 | 60 | - name: color 61 | type: Enum 62 | access: Read-only 63 | comment: | 64 | The color associated with the status update. 65 | example_values: 66 | - "'green'" 67 | - "'yellow'" 68 | - "'red'" 69 | 70 | - name: created_by 71 | <<: *PropType.User 72 | access: Read-only 73 | comment: | 74 | The creator of the status update. 75 | example_values: 76 | - "{ id: 12345, name: 'Tim Bizarro' }" 77 | 78 | - name: created_at 79 | <<: *PropType.DateTime 80 | access: Read-only 81 | comment: | 82 | The time at which the status update was created. 83 | 84 | action_classes: 85 | - name: Create a status update 86 | url: create 87 | - name: Get status updates for a project 88 | url: query 89 | - name: Get a status update 90 | url: get-single 91 | - name: Delete a status update 92 | url: delete 93 | 94 | actions: 95 | 96 | # Create, Retrieve, Update, Delete 97 | 98 | - name: createInProject 99 | class: create 100 | method: POST 101 | path: "/projects/%s/project_statuses" 102 | params: 103 | - name: project 104 | <<: *Param.ProjectGid 105 | required: true 106 | comment: The project on which to create a status update. 107 | - name: text 108 | type: String 109 | required: true 110 | comment: | 111 | The text of the project status update. 112 | example_values: 113 | - "The project is on track to ship next month!" 114 | - name: color 115 | <<: *Param.ProjectStatusColor 116 | required: true 117 | comment: | 118 | The color to associate with the status update. Must be one of `"red"`, `"yellow"`, or `"green"`. 119 | comment: | 120 | Creates a new status update on the project. 121 | 122 | Returns the full record of the newly created project status update. 123 | 124 | - name: findByProject 125 | class: query 126 | method: GET 127 | path: "/projects/%s/project_statuses" 128 | params: 129 | - name: project 130 | <<: *Param.ProjectGid 131 | required: true 132 | comment: The project to find status updates for. 133 | collection: true 134 | comment: | 135 | Returns the compact project status update records for all updates on the project. 136 | 137 | - name: findById 138 | class: get-single 139 | method: GET 140 | path: "/project_statuses/%s" 141 | params: 142 | - name: project-status 143 | <<: *Param.ProjectStatusUpdateGid 144 | required: true 145 | comment: The project status update to get. 146 | comment: | 147 | Returns the complete record for a single status update. 148 | 149 | - name: delete 150 | class: delete 151 | method: DELETE 152 | path: "/project_statuses/%s" 153 | params: 154 | - name: project-status 155 | <<: *Param.ProjectStatusUpdateGid 156 | required: true 157 | comment: The project status update to delete. 158 | comment: | 159 | Deletes a specific, existing project status update. 160 | 161 | Returns an empty data record. 162 | -------------------------------------------------------------------------------- /src/resources/attachment.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | !include ../includes.yaml 3 | name: attachment 4 | comment: | 5 | An _attachment_ object represents any file attached to a task in Asana, 6 | whether it's an uploaded file or one associated via a third-party service 7 | such as Dropbox or Google Drive. 8 | properties: 9 | 10 | - name: id 11 | <<: *PropType.Id 12 | comment: | 13 | Globally unique ID of the attachment. 14 | **Note: This field is under active migration to the [`gid` field](#field-gid)--please see our [blog post](/developers/documentation/getting-started/deprecations) for more information.** 15 | 16 | - name: gid 17 | <<: *PropType.Gid 18 | comment: | 19 | Globally unique ID of the attachment. 20 | 21 | - name: resource_type 22 | <<: *PropType.ResourceType 23 | comment: | 24 | The resource type of this resource. The value for this resource is always `attachment`. 25 | example_values: 26 | - '"attachment"' 27 | values: 28 | - name: attachment 29 | comment: An attachment resource type. 30 | 31 | - name: created_at 32 | <<: *PropType.DateTime 33 | access: Read-only 34 | comment: | 35 | The time at which this attachment was uploaded. 36 | 37 | - name: download_url 38 | type: String 39 | example_values: 40 | - "'https://www.dropbox.com/s/123/Screenshot.png?dl=1'" 41 | - "null" 42 | access: Read-only 43 | comment: | 44 | The URL containing the content of the attachment. 45 | notes: 46 | - | 47 | May be `null` if the attachment is hosted by box. If present, this URL 48 | may only be valid for 1 hour from the time of retrieval. You should avoid 49 | persisting this URL somewhere and just refresh it on demand to ensure you 50 | do not keep stale URLs. 51 | 52 | - name: host 53 | type: String 54 | example_values: 55 | - "'dropbox'" 56 | access: Read-only 57 | comment: | 58 | The service hosting the attachment. Valid values are `asana`, `dropbox`, 59 | `gdrive` and `box`. 60 | 61 | - name: name 62 | type: String 63 | example_values: 64 | - "'Screenshot.png'" 65 | access: Read-only 66 | comment: | 67 | The name of the file. 68 | 69 | - name: parent 70 | <<: *PropType.Task 71 | comment: | 72 | The task this attachment is attached to. 73 | 74 | - name: view_url 75 | type: String 76 | example_values: 77 | - "'https://www.dropbox.com/s/123/Screenshot.png'" 78 | - "null" 79 | access: Read-only 80 | comment: | 81 | The URL where the attachment can be viewed, which may be friendlier to 82 | users in a browser than just directing them to a raw file. 83 | 84 | action_classes: 85 | - name: Get single attachment 86 | url: get-single 87 | - name: Get all attachments 88 | url: get-all-task 89 | - name: Upload an attachment 90 | url: upload 91 | 92 | actions: 93 | 94 | # Create, Retrieve, Update, Delete 95 | 96 | - name: findById 97 | class: get-single 98 | method: GET 99 | path: "/attachments/%s" 100 | params: 101 | - name: attachment 102 | <<: *Param.AttachmentGid 103 | required: true 104 | comment: | 105 | Returns the full record for a single attachment. 106 | 107 | - name: findByTask 108 | class: get-all-task 109 | method: GET 110 | path: "/tasks/%s/attachments" 111 | collection: true 112 | params: 113 | - name: task 114 | <<: *Param.TaskGid 115 | required: true 116 | comment: | 117 | Returns the compact records for all attachments on the task. 118 | 119 | - name: createOnTask 120 | class: upload 121 | method: POST 122 | path: "/tasks/%s/attachments" 123 | no_code: true # Uploading attachments must be hand-coded 124 | params: 125 | - name: task 126 | <<: *Param.TaskGid 127 | required: true 128 | - name: file 129 | type: File 130 | example_values: 131 | - file.txt 132 | required: true 133 | comment: The file you want to upload. 134 | notes: 135 | - | 136 | **When using curl:** 137 | 138 | Be sure to add an '@' before the file path, and use the --form 139 | option instead of the -d option. 140 | 141 | When uploading PDFs with curl, force the content-type to be pdf by 142 | appending the content type to the file path: --form 143 | "file=@file.pdf;type=application/pdf". 144 | comment: | 145 | This method uploads an attachment to a task and returns the compact 146 | record for the created attachment object. It is not possible to attach 147 | files from third party services such as Dropbox, Box & Google Drive via 148 | the API. You must download the file content first and then upload it as any 149 | other attachment. 150 | 151 | The 100MB size limit on attachments in Asana is enforced on this endpoint. 152 | notes: 153 | - | 154 | This endpoint expects a multipart/form-data encoded request containing 155 | the full contents of the file to be uploaded. 156 | 157 | Below is an example of what a well formed multipart/form-data encoded 158 | request might look like. 159 | 160 | Authorization: Bearer \ 161 | Content-Type: multipart/form-data; boundary=\ 162 | User-Agent: Java/1.7.0_76\ 163 | Host: localhost\ 164 | Accept: */*\ 165 | Connection: keep-alive\ 166 | Content-Length: 141 167 | 168 | --\ 169 | Content-Disposition: form-data; name="file"; filename="example.txt"\ 170 | Content-Type: text/plain 171 | 172 | 173 | 174 | ---- 175 | 176 | Requests made should follow the HTTP/1.1 specification that line terminators are of the form `CRLF` or `\r\n` 177 | outlined [here](http://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-01#Basic-Rules) in order for the server 178 | to reliably and properly handle the request. 179 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Asana API Metadata [![Build Status][travis-image]][travis-url] 2 | Metadata for Asana API for generating client libraries and documentation 3 | 4 | This repository contains descriptions of the various resources in the API and their endpoints. The metadata is rich in structural information and comments, such that we can use it to build both documentation and functioning client library objects. 5 | 6 | It is currently used to build the following client libraries: 7 | 8 | * [`java-asana`](https://github.com/Asana/java-asana) 9 | * [`node-asana`](https://github.com/Asana/node-asana) 10 | * [`php-asana`](https://github.com/Asana/php-asana) 11 | * [`python-asana`](https://github.com/Asana/python-asana) 12 | * [`ruby-asana`](https://github.com/Asana/ruby-asana) 13 | 14 | It is also used to build the [Asana API Reference](https://asana.com/developers/api-reference) in the developer documentation. 15 | 16 | ## Contributing 17 | 18 | 1. Clone the repo: 19 | `git clone git@github.com:Asana/asana-api-meta.git` 20 | 2. Make a topic branch: 21 | `git checkout -b my-topic-branch` 22 | 3. Make changes on the topic branch. 23 | 4. Run `gulp build` to build the output for all languages. You can inspect the final output in `dist/`. 24 | 5. When satisfied, make a pull request. 25 | 26 | ## How It Works 27 | 28 | ### Language Configuration 29 | 30 | Each language has its own configuration that determines how the output files are built. These configurations are specified in `gulpfile.js` as `var languages = ...`. Each record has the following schema: 31 | 32 | * `repo`: Name of the target repository where built files will be pushed to. 33 | * `branch`: Name of the branch in the repository to push to. 34 | * `outputPath`: Path, relative to the root of `repo`, where template output will go. 35 | * `preserveRepoFiles`: Set to true if when the files are built and pushed to the target, any existing files are preserved. If false, it will clear out `outputPath` each time it pushes. 36 | * `skip`: An array of resource names to avoid generating output files for. 37 | 38 | ### Resource Definitions 39 | 40 | Resources are defined in individual YAML files under `src/resources`. The one-resource-per-file convention keeps the files manageable. 41 | 42 | A schema is provided for resources, and the resources are validated against it during testing. It can be found in `test/resource_schema.json`, and uses the [JSON Schema](http://json-schema.org/) syntax, with the [`jsonschema`](http://json-schema.org/) Node package for validation. 43 | 44 | The schemas make heavy use of the "anchor" and "alias" features of YAML, so the files can be more normalized and avoid excessive repetition. These are mostly used to define commonly-used components like types, properties, and parameters that appear in multiple (sometimes many, many) places. 45 | 46 | Definitions for value types (like `Email`, `ProjectColor`, `TeamId` etc.) that may appear as a parameter or resource property in more than one place should go in the `src/includes.yaml` file. 47 | 48 | ### Templates 49 | 50 | This module uses templates for generating library source files from the resources. 51 | 52 | The build system will, for each language `LANG` it is building (e.g. `LANG='js'`): 53 | 1. For each resource: 54 | 2. Read in the resource definition file, `src/resources/NAME.yaml`. 55 | 3. Read in the template definition file, `src/templates/LANG/index.js`. 56 | 4. Find the `resource` key. 57 | 5. Read in the `template` to find the input template, and the `filename` function to generate the output filename. 58 | 4. Execute the template against the resource definition. 59 | 5. Output the result into the file `dist/LANG/OUTPUTFILE`. 60 | 61 | All templates use the [`lodash`](https://www.npmjs.com/package/lodash) library for generation. `gulpfile.js` has the build rules that execute the templates. It provides various helpers to the template that are configurable on a per-library basis, by scoping the file `helpers.js` into the template namespace. These include utilities for common code-generation patterns. 62 | 63 | Authors modifying the templates should ensure that they *generate pretty code*, at the expense of the prettiness of the template. Trivial issues like bad indents in the output should be fixed. 64 | 65 | ### Helpers 66 | 67 | Rather than pour a bunch of logic into the template, it's better style to put them into helpers. Currently there is only a single `helpers` file, but we should break this into a set of language-specific files (that might each call some useful common helpers where appropriate). 68 | 69 | Examples of places where extracting helpers is useful are `paramsForAction` or `wrapComment`. 70 | 71 | ## Owner Workflow 72 | 73 | ### Testing Proposed Changes 74 | 75 | 1. Get a personal access token for GitHub and assign it to the environment variable `ASANA_GITHUB_TOKEN`: 76 | `export ASANA_GITHUB_TOKEN=...` 77 | 2. Run a test deployment for a single language, e.g. `gulp deploy-js`. This will create a new branch in the target repo and deploy the generated files to that branch. The branch will be named for your GitHub username and a date-timestamp, for example `asanabot-20150531-012345`. 78 | 3. Inspect the diffs on GitHub. If you need to make changes you can re-run the deploy and it will create a new branch. 79 | 4. You can do a test deploy to all languages at once by running just `gulp deploy`. 80 | 81 | ### Committing 82 | 83 | 1. Push changes to origin `git push origin my-topic-branch`. 84 | 2. Make a pull request in GitHub. This will automatically create a task in Asana. 85 | 3. Once your request is reviewed, it can be merged. 86 | 87 | ### Deploying 88 | 89 | 1. Clone the repo, work on master. 90 | 2. Bump the package version to indicate the [semantic version](http://semver.org/) change, using one of: `gulp bump-patch`, `gulp bump-feature`, or `gulp bump-release` 91 | 3. Push changes to origin, including tags: 92 | `git push origin master --tags` 93 | 94 | ### Propagating Changes to Client Libraries 95 | 96 | 1. Travis will automatically build and deploy new code to the `api-meta-incoming` branch of all the repos, creating pull requests for each. 97 | 2. Review and merge the pull requests as appropriate. 98 | 3. Update package versions according to [semantic versioning](http://semver.org/), and push. 99 | 100 | 101 | [travis-url]: http://travis-ci.org/Asana/asana-api-meta 102 | [travis-image]: https://api.travis-ci.org/Asana/asana-api-meta.svg?style=flat-square&branch=master 103 | -------------------------------------------------------------------------------- /src/resources/tag.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # See `user.yaml` for more docs on these yaml files. 3 | !include ../includes.yaml 4 | name: tag 5 | comment: | 6 | A _tag_ is a label that can be attached to any task in Asana. It exists in a 7 | single workspace or organization. 8 | 9 | Tags have some metadata associated with them, but it is possible that we will 10 | simplify them in the future so it is not encouraged to rely too heavily on it. 11 | Unlike projects, tags do not provide any ordering on the tasks they 12 | are associated with. 13 | 14 | properties: 15 | # We borrow a bunch of the project templates here because they're literally 16 | # exactly the same 17 | 18 | - name: id 19 | <<: *PropType.Id 20 | comment: | 21 | Globally unique ID of the tag. 22 | **Note: This field is under active migration to the [`gid` field](#field-gid)--please see our [blog post](/developers/documentation/getting-started/deprecations) for more information.** 23 | 24 | - name: gid 25 | <<: *PropType.Gid 26 | comment: | 27 | Globally unique ID of the tag. 28 | 29 | - name: resource_type 30 | <<: *PropType.ResourceType 31 | comment: | 32 | The resource type of this resource. The value for this resource is always `tag`. 33 | example_values: 34 | - '"tag"' 35 | values: 36 | - name: tag 37 | comment: A tag resource type. 38 | 39 | - name: created_at 40 | <<: *PropType.DateTime 41 | access: Read-only 42 | comment: | 43 | The time at which this tag was created. 44 | 45 | - name: followers 46 | <<: *PropType.UserArray 47 | access: Read-only 48 | comment: | 49 | Array of users following this tag. 50 | 51 | - name: name 52 | <<: *PropType.PotName 53 | comment: | 54 | Name of the tag. This is generally a short sentence fragment that fits 55 | on a line in the UI for maximum readability. However, it can be longer. 56 | 57 | - name: color 58 | <<: *PropType.PotColor 59 | comment: | 60 | Color of the tag. Must be either `null` or one of: `dark-pink`, 61 | `dark-green`, `dark-blue`, `dark-red`, `dark-teal`, `dark-brown`, 62 | `dark-orange`, `dark-purple`, `dark-warm-gray`, `light-pink`, `light-green`, 63 | `light-blue`, `light-red`, `light-teal`, `light-yellow`, `light-orange`, 64 | `light-purple`, `light-warm-gray`. 65 | 66 | - name: workspace 67 | <<: *PropType.Workspace 68 | comment: | 69 | The workspace or organization this tag is associated with. Once created, 70 | tags cannot be moved to a different workspace. This attribute can only 71 | be specified at creation time. 72 | 73 | action_classes: 74 | - name: Create a tag 75 | url: create 76 | - name: Get a single tag 77 | url: get-single 78 | - name: Update a tag 79 | url: update 80 | - name: Query for tags 81 | url: query 82 | 83 | actions: 84 | 85 | # Create, Retrieve, Update, Delete 86 | 87 | - name: create 88 | class: create 89 | method: POST 90 | path: "/tags" 91 | params: 92 | - name: workspace 93 | <<: *Param.WorkspaceGid 94 | required: true 95 | comment: The workspace or organization to create the tag in. 96 | comment: | 97 | Creates a new tag in a workspace or organization. 98 | 99 | Every tag is required to be created in a specific workspace or 100 | organization, and this cannot be changed once set. Note that you can use 101 | the `workspace` parameter regardless of whether or not it is an 102 | organization. 103 | 104 | Returns the full record of the newly created tag. 105 | 106 | - name: createInWorkspace 107 | class: create 108 | method: POST 109 | path: "/workspaces/%s/tags" 110 | params: 111 | - name: workspace 112 | <<: *Param.WorkspaceGid 113 | required: true 114 | comment: The workspace or organization to create the tag in. 115 | comment: | 116 | Creates a new tag in a workspace or organization. 117 | 118 | Every tag is required to be created in a specific workspace or 119 | organization, and this cannot be changed once set. Note that you can use 120 | the `workspace` parameter regardless of whether or not it is an 121 | organization. 122 | 123 | Returns the full record of the newly created tag. 124 | 125 | - name: findById 126 | class: get-single 127 | method: GET 128 | path: "/tags/%s" 129 | params: 130 | - name: tag 131 | <<: *Param.TagGid 132 | required: true 133 | comment: The tag to get. 134 | comment: | 135 | Returns the complete tag record for a single tag. 136 | 137 | - name: update 138 | class: update 139 | method: PUT 140 | path: "/tags/%s" 141 | params: 142 | - name: tag 143 | <<: *Param.TagGid 144 | required: true 145 | comment: The tag to update. 146 | comment: | 147 | Updates the properties of a tag. Only the fields provided in the `data` 148 | block will be updated; any unspecified fields will remain unchanged. 149 | 150 | When using this method, it is best to specify only those fields you wish 151 | to change, or else you may overwrite changes made by another user since 152 | you last retrieved the task. 153 | 154 | Returns the complete updated tag record. 155 | 156 | - name: delete 157 | class: delete 158 | method: DELETE 159 | path: "/tags/%s" 160 | params: 161 | - name: tag 162 | <<: *Param.TagGid 163 | required: true 164 | comment: The tag to delete. 165 | comment: | 166 | A specific, existing tag can be deleted by making a DELETE request 167 | on the URL for that tag. 168 | 169 | Returns an empty data record. 170 | 171 | - name: findAll 172 | class: query 173 | method: GET 174 | path: "/tags" 175 | collection: true 176 | comment: | 177 | Returns the compact tag records for some filtered set of tags. 178 | Use one or more of the parameters provided to filter the tags returned. 179 | params: 180 | - name: workspace 181 | <<: *Param.WorkspaceGid 182 | comment: The workspace or organization to filter tags on. 183 | - name: team 184 | <<: *Param.TeamGid 185 | comment: The team to filter tags on. 186 | - name: archived 187 | <<: *Param.Bool 188 | comment: | 189 | Only return tags whose `archived` field takes on the value of 190 | this parameter. 191 | 192 | - name: findByWorkspace 193 | class: query 194 | method: GET 195 | path: "/workspaces/%s/tags" 196 | params: 197 | - name: workspace 198 | <<: *Param.WorkspaceGid 199 | required: true 200 | comment: The workspace or organization to find tags in. 201 | collection: true 202 | comment: | 203 | Returns the compact tag records for all tags in the workspace. 204 | -------------------------------------------------------------------------------- /src/resources/story.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | !include ../includes.yaml 3 | name: story 4 | comment: | 5 | A _story_ represents an activity associated with an object in the Asana 6 | system. Stories are generated by the system whenever users take actions such 7 | as creating or assigning tasks, or moving tasks between projects. _Comments_ 8 | are also a form of user-generated story. 9 | 10 | properties: 11 | 12 | - name: id 13 | <<: *PropType.Id 14 | comment: | 15 | Globally unique ID of the story. 16 | **Note: This field is under active migration to the [`gid` field](#field-gid)--please see our [blog post](/developers/documentation/getting-started/deprecations) for more information.** 17 | 18 | - name: gid 19 | <<: *PropType.Gid 20 | comment: | 21 | Globally unique ID of the story. 22 | 23 | - name: resource_type 24 | <<: *PropType.ResourceType 25 | comment: | 26 | The resource type of this resource. The value for this resource is always `story`. 27 | example_values: 28 | - '"story"' 29 | values: 30 | - name: story 31 | comment: A story resource type. 32 | 33 | - name: resource_subtype 34 | <<: *PropType.ResourceSubtype 35 | access: Read-only 36 | comment: | 37 | The type of story. This provides fine-grained information about what triggered the story's creation. There are many story subtypes, so inspect the data returned from Asana's API to find the value for your use case. 38 | example_values: 39 | - '"comment_added"' 40 | - '"description_changed"' 41 | - '"liked"' 42 | - '...' 43 | 44 | - name: created_at 45 | <<: *PropType.DateTime 46 | comment: | 47 | The time at which this story was created. 48 | 49 | - name: created_by 50 | <<: *PropType.User 51 | comment: | 52 | The user who created the story. 53 | 54 | - name: liked 55 | <<: *PropType.Bool 56 | comment: | 57 | True if the story is liked by the authorized user, false if not. 58 | notes: 59 | - | 60 | This property only exists for stories that provide likes. 61 | 62 | - name: likes 63 | <<: *PropType.UserArray 64 | access: Read-only 65 | comment: | 66 | Array of users who have liked this story. 67 | notes: 68 | - | 69 | This property only exists for stories that provide likes. 70 | 71 | - name: num_likes 72 | <<: *PropType.Count 73 | access: Read-only 74 | comment: | 75 | The number of users who have liked this story. 76 | notes: 77 | - | 78 | This property only exists for stories that provide likes. 79 | 80 | - name: text 81 | type: String 82 | example_values: 83 | - "'marked today'" 84 | access: Create-only 85 | comment: | 86 | Human-readable text for the story or comment. This will not include the 87 | name of the creator. 88 | notes: 89 | - | 90 | This is not guaranteed to be stable for a given type of story. For 91 | example, text for a reassignment may **not** always say “assigned to …” 92 | as the text for a story can both be edited and change based on the language settings 93 | of the user making the request. 94 | Use the `resource_subtype` property to discover the action that created the story. 95 | 96 | - name: html_text 97 | <<: *PropType.HtmlText 98 | comment: | 99 | [Opt In](https://asana.com/developers/documentation/getting-started/input-output-options). HTML formatted text for a comment. 100 | 101 | - name: target 102 | <<: *PropType.Task 103 | comment: | 104 | The object this story is associated with. Currently may only be a task. 105 | 106 | - name: is_pinned 107 | <<: *PropType.Bool 108 | comment: | 109 | Whether the story is pinned on the target. 110 | notes: 111 | - This field is only present on comment and attachment stories. 112 | 113 | - name: is_edited 114 | <<: *PropType.Bool 115 | comment: | 116 | Whether the text of the story has been edited after creation. 117 | notes: 118 | - This field is only present on comment stories. 119 | 120 | - name: source 121 | <<: *PropType.StorySource 122 | comment: | 123 | The component of the Asana product the user used to create the story. 124 | 125 | - name: type 126 | <<: *PropType.StoryType 127 | comment: | 128 | **Deprecated: new integrations should prefer the `resource_subtype` field.** 129 | The type of this story. For more fine-grained inspection of story types, see the [`resource_subtype`](#field-resource_subtype) property. 130 | 131 | action_classes: 132 | - name: Get stories on object 133 | url: get-all 134 | - name: Get a single story 135 | url: get-single 136 | - name: Commenting on an object 137 | url: post-comment 138 | - name: Update a story 139 | url: update 140 | - name: Delete a story 141 | url: delete 142 | 143 | actions: 144 | 145 | # Create, Retrieve, Update, Delete 146 | 147 | - name: findByTask 148 | class: get-all 149 | method: GET 150 | path: "/tasks/%s/stories" 151 | collection: true 152 | params: 153 | - name: task 154 | <<: *Param.TaskGid 155 | required: true 156 | comment: | 157 | Returns the compact records for all stories on the task. 158 | 159 | - name: findById 160 | class: get-single 161 | method: GET 162 | path: "/stories/%s" 163 | params: 164 | - name: story 165 | <<: *Param.StoryGid 166 | required: true 167 | comment: | 168 | Returns the full record for a single story. 169 | 170 | - name: createOnTask 171 | class: post-comment 172 | <<: *Action.CommentOnTask 173 | 174 | - name: update 175 | class: update 176 | method: PUT 177 | path: "/stories/%s" 178 | params: 179 | - name: story 180 | <<: *Param.StoryGid 181 | required: true 182 | - name: text 183 | type: String 184 | example_values: 185 | - "'This is a comment.'" 186 | comment: | 187 | The plain text with which to update the comment. 188 | - name: html_text 189 | type: String 190 | example_values: 191 | - "'Get whatever <a href='https://app.asana.com/0/1123/1234'>Sashimi</a> has.'" 192 | comment: The rich text with which to update the comment. 193 | - name: is_pinned 194 | <<: *Param.Bool 195 | comment: Whether the story should be pinned on the resource. 196 | comment: | 197 | Updates the story and returns the full record for the updated story. 198 | Only comment stories can have their text updated, and only comment stories and 199 | attachment stories can be pinned. Only one of `text` and `html_text` can be specified. 200 | 201 | - name: delete 202 | class: delete 203 | method: DELETE 204 | path: "/stories/%s" 205 | params: 206 | - name: story 207 | <<: *Param.StoryGid 208 | required: true 209 | comment: | 210 | Deletes a story. A user can only delete stories they have created. Returns an empty data record. 211 | -------------------------------------------------------------------------------- /src/resources/section.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # See `user.yaml` for more docs on these yaml files. 3 | !include ../includes.yaml 4 | name: section 5 | comment: | 6 | A _section_ is a subdivision of a project that groups tasks together. It can 7 | either be a header above a list of tasks in a list view or a column in a 8 | board view of a project. 9 | 10 | notes: 11 | - | 12 | Sections are largely a shared idiom in Asana's API for both list and board 13 | views of a project regardless of the project's layout. 14 | 15 | The 'memberships' property when [getting a 16 | task](/developers/api-reference/tasks#get) will return the information for 17 | the section or the column under 'section' in the response. 18 | 19 | properties: 20 | 21 | - name: id 22 | <<: *PropType.Id 23 | comment: | 24 | Globally unique ID of the section. 25 | **Note: This field is under active migration to the [`gid` field](#field-gid)--please see our [blog post](/developers/documentation/getting-started/deprecations) for more information.** 26 | - name: gid 27 | <<: *PropType.Gid 28 | comment: | 29 | Globally unique ID of the section. 30 | - name: resource_type 31 | <<: *PropType.ResourceType 32 | comment: | 33 | The resource type of this resource. The value for this resource is always `section`. 34 | example_values: 35 | - '"section"' 36 | values: 37 | - name: section 38 | comment: A section resource type. 39 | - name: name 40 | <<: *PropType.SectionName 41 | comment: | 42 | The name of the section (i.e. the text displayed as the section header). 43 | 44 | - name: project 45 | <<: *PropType.Project 46 | access: Read-only 47 | comment: | 48 | The project which contains the section. 49 | 50 | - name: created_at 51 | <<: *PropType.DateTime 52 | access: Read-only 53 | comment: | 54 | The time at which the section was created. 55 | 56 | 57 | action_classes: 58 | - name: Create a section 59 | url: create 60 | - name: Get sections in a project 61 | url: find-project 62 | - name: Get a single section 63 | url: get-single 64 | - name: Update a section 65 | url: update 66 | - name: Delete a section 67 | url: delete 68 | - name: Move a section in a project 69 | url: reorder 70 | 71 | actions: 72 | 73 | # Create, Retrieve, Update, Delete 74 | 75 | - name: createInProject 76 | class: create 77 | method: POST 78 | path: "/projects/%s/sections" 79 | params: 80 | - name: project 81 | <<: *Param.ProjectGid 82 | required: true 83 | comment: The project to create the section in 84 | - name: name 85 | <<: *Param.SectionName 86 | comment: The text to be displayed as the section name. This cannot be an empty string. 87 | required: true 88 | comment: | 89 | Creates a new section in a project. 90 | 91 | Returns the full record of the newly created section. 92 | 93 | - name: findByProject 94 | class: find-project 95 | method: GET 96 | path: "/projects/%s/sections" 97 | params: 98 | - name: project 99 | <<: *Param.ProjectGid 100 | required: true 101 | comment: The project to get sections from. 102 | collection: true 103 | comment: | 104 | Returns the compact records for all sections in the specified project. 105 | 106 | - name: findById 107 | class: get-single 108 | method: GET 109 | path: "/sections/%s" 110 | params: 111 | - name: section 112 | <<: *Param.SectionGid 113 | required: true 114 | comment: The section to get. 115 | comment: | 116 | Returns the complete record for a single section. 117 | 118 | - name: update 119 | class: update 120 | method: PUT 121 | path: "/sections/%s" 122 | params: 123 | - name: section 124 | <<: *Param.SectionGid 125 | required: true 126 | comment: The section to update. 127 | comment: | 128 | A specific, existing section can be updated by making a PUT request on 129 | the URL for that project. Only the fields provided in the `data` block 130 | will be updated; any unspecified fields will remain unchanged. (note that 131 | at this time, the only field that can be updated is the `name` field.) 132 | 133 | When using this method, it is best to specify only those fields you wish 134 | to change, or else you may overwrite changes made by another user since 135 | you last retrieved the task. 136 | 137 | Returns the complete updated section record. 138 | 139 | - name: delete 140 | class: delete 141 | method: DELETE 142 | path: "/sections/%s" 143 | params: 144 | - name: section 145 | <<: *Param.SectionGid 146 | required: true 147 | comment: The section to delete. 148 | comment: | 149 | A specific, existing section can be deleted by making a DELETE request 150 | on the URL for that section. 151 | 152 | Note that sections must be empty to be deleted. 153 | 154 | The last remaining section in a board view cannot be deleted. 155 | 156 | Returns an empty data block. 157 | 158 | - name: addTask 159 | class: reorder 160 | method: POST 161 | path: "/sections/%s/addTask" 162 | params: 163 | - name: section 164 | <<: *Param.SectionGid 165 | required: true 166 | comment: The section in which to add the task 167 | - name: task 168 | <<: *Param.TaskGid 169 | required: true 170 | comment: The task to add to this section 171 | - name: insert_before 172 | <<: *Param.Gid 173 | comment: Insert the given task immediately before the task specified by this parameter. Cannot be provided together with `insert_after`. 174 | - name: insert_after 175 | <<: *Param.Gid 176 | comment: Insert the given task immediately after the task specified by this parameter. Cannot be provided together with `insert_before`. 177 | comment: | 178 | Add a task to a specific, existing section. This will remove the task from other sections of the project. 179 | 180 | The task will be inserted at the top of a section unless an `insert_before` or `insert_after` parameter is declared. 181 | 182 | This does not work for separators (tasks with the `resource_subtype` of section). 183 | 184 | - name: insertInProject 185 | class: reorder 186 | method: POST 187 | path: "/projects/%s/sections/insert" 188 | params: 189 | - name: project 190 | <<: *Param.ProjectGid 191 | required: true 192 | comment: The project in which to reorder the given section 193 | - name: section 194 | <<: *Param.SectionGid 195 | required: true 196 | comment: The section to reorder 197 | - name: before_section 198 | <<: *Param.SectionGid 199 | required: false 200 | example_values: 201 | - "86420" 202 | comment: Insert the given section immediately before the section specified by this parameter. 203 | - name: after_section 204 | <<: *Param.SectionGid 205 | required: false 206 | example_values: 207 | - "86420" 208 | comment: Insert the given section immediately after the section specified by this parameter. 209 | comment: | 210 | Move sections relative to each other in a board view. One of 211 | `before_section` or `after_section` is required. 212 | 213 | Sections cannot be moved between projects. 214 | 215 | At this point in time, moving sections is not supported in list views, only board views. 216 | 217 | Returns an empty data block. 218 | -------------------------------------------------------------------------------- /src/resources/workspace.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | !include ../includes.yaml 3 | name: workspace 4 | comment: | 5 | A _workspace_ is the highest-level organizational unit in Asana. All projects 6 | and tasks have an associated workspace. 7 | 8 | An _organization_ is a special kind of workspace that represents a company. 9 | In an organization, you can group your projects into teams. You can read 10 | more about how organizations work on the Asana Guide. 11 | To tell if your workspace is an organization or not, check its 12 | `is_organization` property. 13 | 14 | Over time, we intend to migrate most workspaces into organizations and to 15 | release more organization-specific functionality. We may eventually deprecate 16 | using workspace-based APIs for organizations. Currently, and until after 17 | some reasonable grace period following any further announcements, you can 18 | still reference organizations in any `workspace` parameter. 19 | 20 | properties: 21 | 22 | - name: id 23 | <<: *PropType.Id 24 | comment: | 25 | Globally unique ID of the workspace. 26 | **Note: This field is under active migration to the [`gid` field](#field-gid)--please see our [blog post](/developers/documentation/getting-started/deprecations) for more information.** 27 | 28 | - name: gid 29 | <<: *PropType.Gid 30 | comment: | 31 | Globally unique ID of the workspace. 32 | 33 | - name: resource_type 34 | <<: *PropType.ResourceType 35 | comment: | 36 | The resource type of this resource. The value for this resource is always `workspace`. 37 | example_values: 38 | - '"workspace"' 39 | values: 40 | - name: workspace 41 | comment: A workspace resource type. 42 | 43 | - name: name 44 | type: String 45 | example_values: 46 | - "'My Favorite Workspace'" 47 | comment: | 48 | The name of the workspace. 49 | 50 | - name: is_organization 51 | <<: *PropType.Bool 52 | comment: | 53 | Whether the workspace is an _organization_. 54 | 55 | action_classes: 56 | - name: Get available workspaces 57 | url: get 58 | - name: Update a workspace 59 | url: update 60 | - name: Typeahead search 61 | url: typeahead 62 | - name: User Management 63 | url: user-mgmt 64 | - name: Get workspaces for user 65 | url: get-all-user 66 | 67 | actions: 68 | 69 | # Create, Retrieve, Update, Delete 70 | 71 | - name: findById 72 | class: get 73 | method: GET 74 | path: "/workspaces/%s" 75 | params: 76 | - name: workspace 77 | <<: *Param.WorkspaceId 78 | required: true 79 | comment: | 80 | Returns the full workspace record for a single workspace. 81 | 82 | - name: findAll 83 | class: get 84 | method: GET 85 | path: "/workspaces" 86 | collection: true 87 | comment: | 88 | Returns the compact records for all workspaces visible to the authorized user. 89 | 90 | - name: update 91 | class: update 92 | method: PUT 93 | path: "/workspaces/%s" 94 | params: 95 | - name: workspace 96 | <<: *Param.WorkspaceId 97 | required: true 98 | comment: The workspace to update. 99 | comment: | 100 | A specific, existing workspace can be updated by making a PUT request on 101 | the URL for that workspace. Only the fields provided in the data block 102 | will be updated; any unspecified fields will remain unchanged. 103 | 104 | Currently the only field that can be modified for a workspace is its `name`. 105 | 106 | Returns the complete, updated workspace record. 107 | 108 | - name: typeahead 109 | class: typeahead 110 | method: GET 111 | path: "/workspaces/%s/typeahead" 112 | params: 113 | - name: workspace 114 | <<: *Param.WorkspaceId 115 | required: true 116 | comment: The workspace to fetch objects from. 117 | - name: resource_type 118 | type: Enum 119 | example_values: 120 | - user 121 | values: 122 | - name: custom_field 123 | comment: A custom field. 124 | - name: portfolio 125 | comment: A portfolio. 126 | - name: project 127 | comment: A project. 128 | - name: tag 129 | comment: A tag. 130 | - name: task 131 | comment: A task. 132 | - name: user 133 | comment: A user. 134 | required: true 135 | comment: | 136 | The type of values the typeahead should return. You can choose from 137 | one of the following: custom_field, project, tag, task, and user. 138 | Note that unlike in the names of endpoints, the types listed here are 139 | in singular form (e.g. `task`). Using multiple types is not yet supported. 140 | - name: type 141 | type: Enum 142 | example_values: 143 | - user 144 | values: 145 | - name: custom_field 146 | comment: A custom field. 147 | - name: portfolio 148 | comment: A portfolio. 149 | - name: project 150 | comment: A project. 151 | - name: tag 152 | comment: A tag. 153 | - name: task 154 | comment: A task. 155 | - name: user 156 | comment: A user. 157 | required: false 158 | comment: | 159 | **Deprecated: new integrations should prefer the resource_type field.** 160 | - name: query 161 | type: String 162 | example_values: 163 | - Greg 164 | comment: | 165 | The string that will be used to search for relevant objects. If an 166 | empty string is passed in, the API will currently return an empty 167 | result set. 168 | - name: count 169 | type: Number 170 | example_values: 171 | - "10" 172 | comment: | 173 | The number of results to return. The default is `20` if this 174 | parameter is omitted, with a minimum of `1` and a maximum of `100`. 175 | If there are fewer results found than requested, all will be returned. 176 | collection: true 177 | collection_cannot_paginate: true 178 | comment: | 179 | Retrieves objects in the workspace based on an auto-completion/typeahead 180 | search algorithm. This feature is meant to provide results quickly, so do 181 | not rely on this API to provide extremely accurate search results. The 182 | result set is limited to a single page of results with a maximum size, 183 | so you won't be able to fetch large numbers of results. 184 | 185 | - name: addUser 186 | class: user-mgmt 187 | method: POST 188 | path: "/workspaces/%s/addUser" 189 | params: 190 | - name: workspace 191 | <<: *Param.WorkspaceId 192 | required: true 193 | comment: The workspace or organization to invite the user to. 194 | - name: user 195 | <<: *Param.User 196 | required: true 197 | comment: | 198 | The user can be referenced by their globally unique user ID or their email address. 199 | Returns the full user record for the invited user. 200 | 201 | - name: removeUser 202 | class: user-mgmt 203 | method: POST 204 | path: "/workspaces/%s/removeUser" 205 | params: 206 | - name: workspace 207 | <<: *Param.WorkspaceId 208 | required: true 209 | comment: The workspace or organization to invite the user to. 210 | - name: user 211 | <<: *Param.User 212 | required: true 213 | comment: | 214 | The user making this call must be an admin in the workspace. 215 | Returns an empty data record. 216 | 217 | - name: findByUser 218 | class: get-all-user 219 | method: GET 220 | path: "/users/%s/workspaces" 221 | collection: true 222 | collection_cannot_paginate: true 223 | params: 224 | - name: user 225 | <<: *Param.User 226 | required: true 227 | comment: The user for which to get workspaces. 228 | comment: | 229 | Returns the workspaces for the given user. 230 | -------------------------------------------------------------------------------- /src/templates/ruby/resource.ejs: -------------------------------------------------------------------------------- 1 | <% 2 | var singularName = resource.name, 3 | pluralName = plural(singularName), 4 | mixins = { 5 | task: ["AttachmentUploading", "EventSubscription"], 6 | project: ["EventSubscription"] 7 | }, 8 | skip = { attachment: ["createOnTask"] }, 9 | formatComment = function formatComment(text, indentation) { 10 | var indent = Array(indentation + 1).join(" ") 11 | return text.trim().split("\n").map(function(line) { 12 | return indent + (line.length > 0 ? "# " : "#") + line 13 | }).join("\n") 14 | } 15 | 16 | 17 | function Action(action) { 18 | var that = this 19 | this.action = action 20 | this.collection = action.collection == true 21 | this.requiresData = action.method == "POST" || action.method == "PUT" 22 | this.isInstanceAction = (action.method == "PUT" || action.method == "DELETE") 23 | this.method = action.method 24 | this.methodName = snake(action.name) 25 | this.clientMethod = action.method.toLowerCase() 26 | this.returnsUpdatedRecord = action.method == 'PUT' || action.comment.match(/Returns[a-z\W]+updated/) !== null 27 | this.returnsNothing = action.comment.match(/Returns an empty/) !== null 28 | 29 | // Params and idParams 30 | var params = action.params || [] 31 | this.idParams = _.filter(params, function(p) { return p.required || p.type == "Id" || p.type == "Gid" }) 32 | 33 | // If it looks like an instance action but it's not, make it one 34 | if (!this.isInstanceAction) { 35 | var mainIdParam = _.find(this.idParams, function(p) { return p.name == singularName }) 36 | if (mainIdParam !== undefined && !action.name.match(/Id/)) { 37 | this.isInstanceAction = true 38 | this.mainIdParam = mainIdParam 39 | } 40 | } 41 | 42 | if (this.idParams.length == 1 && 43 | action.path.match(/%s/) && 44 | (action.name.match(/Id/) || (this.isInstanceAction && this.mainIdParam == undefined))) { 45 | var mainIdParam = this.idParams[0] 46 | this.mainIdParam = mainIdParam 47 | this.inferredReturnType = this.isInstanceAction ? 'self.class' : 'self' 48 | } 49 | 50 | if (mainIdParam !== undefined) { 51 | this.params = _.reject(params, function(p) { return p.name == mainIdParam.name }) 52 | } else { 53 | this.params = params 54 | } 55 | 56 | if (!this.inferredReturnType) { 57 | // Infer return type 58 | var name = action.path.match(/\/([a-zA-Z]+)$/) 59 | if (name !== null) { 60 | name = name[1] 61 | 62 | // Desugarize 'addProject' to 'project' 63 | var camelCaseTail = name.match(/.*([A-Z][a-z]+)$/) 64 | if (camelCaseTail !== null) { name = decap(camelCaseTail[1]) } 65 | 66 | name = single(name) 67 | 68 | var explicit = _.find(resources, function(p) { return p == name }) 69 | 70 | if (name == singularName || name == 'parent' || name == 'children' || name.match(/^sub/) !== null) { 71 | this.inferredReturnType = this.isInstanceAction ? 'self.class' : 'self' 72 | } else if (explicit !== undefined) { 73 | this.inferredReturnType = cap(explicit) 74 | } else { 75 | this.inferredReturnType = 'Resource' 76 | } 77 | } else { 78 | this.inferredReturnType = 'Resource' 79 | } 80 | } 81 | 82 | // Endpoint path 83 | this.path = _.reduce(this.idParams, function(acc, id) { 84 | var localName = that.mainIdParam == id ? "gid" : id.name 85 | return acc.replace("\%s", "#{" + localName + "}") 86 | }, action.path) 87 | 88 | // Extra params (not in the URL) to be passed in the body of the call 89 | this.extraParams = _.reject(this.params, function(p) { 90 | return that.path.match(new RegExp("#{" + p.name + "}")) 91 | }) 92 | 93 | // Params processing 94 | var paramsLocal = "data" 95 | if (this.collection) { this.extraParams.push({ name: "per_page", apiParamName: "limit" }) } 96 | if (this.extraParams.length > 0) { 97 | var paramNames = _.map(this.extraParams, function(p) { return (p.apiParamName || p.name) + ": " + p.name }) 98 | if (this.requiresData) { 99 | var paramsProcessing = "with_params = data.merge(" + paramNames.join(", ") + ")" 100 | paramsLocal = "with_params" 101 | } else { 102 | var paramsProcessing = "params = { " + paramNames.join(", ") + " }" 103 | paramsLocal = "params" 104 | } 105 | paramsProcessing += ".reject { |_,v| v.nil? || Array(v).empty? }" 106 | } 107 | 108 | this.paramsProcessing = paramsProcessing 109 | this.paramsLocal = paramsLocal 110 | 111 | // Method argument names 112 | var argumentNames = Array() 113 | if (!this.isInstanceAction) { argumentNames.push("client") } 114 | if (this.mainIdParam !== undefined && !this.isInstanceAction) { argumentNames.push("id") } 115 | _.forEach(this.params, function(param) { 116 | argumentNames.push(param.name + ":" + (param.required ? " required(\"" + param.name + "\")" : " nil")) 117 | }) 118 | if (this.collection) { argumentNames.push("per_page: 20") } 119 | if (this.method != 'DELETE') { argumentNames.push("options: {}") } 120 | if (this.requiresData) { argumentNames.push("**data") } 121 | this.argumentNames = argumentNames 122 | 123 | // API request params 124 | var requestParams = Array() 125 | requestParams.push('"' + this.path + '"') 126 | if (this.paramsProcessing || this.argumentNames.indexOf("**data") != -1) { 127 | var argument = this.requiresData ? "body" : "params" 128 | requestParams.push(argument + ": " + paramsLocal) 129 | } 130 | if (this.method != 'DELETE') { requestParams.push("options: options") } 131 | this.requestParams = requestParams 132 | this.documentation = this.renderDocumentation() 133 | 134 | // Constructor 135 | this.constructor = function(body) { 136 | var pre = '', post = '' 137 | var wrapWithParsing = function(body) { 138 | var pre = '', post = '' 139 | if (!that.returnsNothing) { 140 | pre = 'parse(' 141 | post = ')' + (that.collection ? '' : '.first') 142 | } 143 | return pre + body + post 144 | } 145 | 146 | if (!that.returnsNothing) { 147 | if (that.isInstanceAction && that.returnsUpdatedRecord) { 148 | pre = "refresh_with(" 149 | post = ')' 150 | } else { 151 | pre = that.collection ? "Collection.new(" : that.inferredReturnType + ".new(" 152 | post = (that.collection ? ', type: ' + that.inferredReturnType : '') + ', client: client)' 153 | } 154 | } else { post = ' && true' } 155 | return pre + wrapWithParsing(body) + post 156 | } 157 | 158 | this.request = this.constructor("client." + this.clientMethod + "(" + this.requestParams.join(", ") + ")") 159 | } 160 | 161 | Action.prototype.renderDocumentation = function () { 162 | var formatParamNotes = function(params) { 163 | var trimmed = _.flatten(_.map(params, function(p) { 164 | return _.map(p.notes, function(note) { return note.trim() }) 165 | })) 166 | return (trimmed.length > 0 ? "\nNotes:\n\n" + trimmed.join("\n\n") : "") 167 | } 168 | 169 | var formatParam = function(p, name) { 170 | return (name !== undefined ? name : p.name) + " - [" + p.type + "] " + p.comment 171 | } 172 | var lines = _.map(this.params, function(p) { return formatParam(p) }) 173 | if (this.mainIdParam !== undefined && !this.isInstanceAction) { lines.unshift(formatParam(this.mainIdParam, "id")) } 174 | if (this.collection) { lines.push("per_page - [Integer] the number of records to fetch per page.") } 175 | if (this.method != 'DELETE') { lines.push("options - [Hash] the request I/O options.") } 176 | if (this.requiresData) { lines.push("data - [Hash] the attributes to post.") } 177 | return this.action.comment + "\n" + lines.join("\n") + formatParamNotes(this.params) 178 | } 179 | 180 | var actionsToSkip = skip[resource.name] || [] 181 | var actionsToGen = _.reject(resource.actions, function(action) { 182 | return actionsToSkip.indexOf(action.name) != -1 183 | }) 184 | 185 | var allActions = _.map(actionsToGen, function(action) { return new Action(action) }), 186 | instanceActions = _.filter(allActions, function(action) { return action.isInstanceAction }), 187 | classActions = _.reject(allActions, function(action) { return action.isInstanceAction }) 188 | 189 | var mixinsToInclude = mixins[resource.name] || [] 190 | 191 | %>### WARNING: This file is auto-generated by the asana-api-meta repo. Do not 192 | ### edit it manually. 193 | 194 | module Asana 195 | module Resources 196 | <%= formatComment(resource.comment, 4) %> 197 | class <%= single(classify(singularName)) %> < Resource 198 | <% _.forEach(mixinsToInclude, function(mixin) { %> 199 | include <%= mixin %> 200 | <% }) %> 201 | <% _.forEach(resource.properties, function(property) { %> 202 | attr_reader :<%= property.name %> 203 | <% }) %> 204 | class << self 205 | # Returns the plural name of the resource. 206 | def plural_name 207 | '<%= pluralName %>' 208 | end 209 | <% _.forEach(classActions, function(action) { %> 210 | <%= formatComment(action.documentation, 8) %> 211 | def <%= action.methodName %>(<%= action.argumentNames.join(", ") %>) 212 | <% if (action.paramsProcessing) { %> <%= action.paramsProcessing %><% } %> 213 | <%= action.request %> 214 | end 215 | <% }) %> end 216 | <% _.forEach(instanceActions, function(action) { %> 217 | <%= formatComment(action.documentation, 6) %> 218 | def <%= action.methodName %>(<%= action.argumentNames.join(", ") %>) 219 | <% if (action.paramsProcessing) { %> <%= action.paramsProcessing %><% } %> 220 | <%= action.request %> 221 | end 222 | <% }) %> 223 | end 224 | end 225 | end 226 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var bump = require('gulp-bump'); 2 | var exec = require('child_process').exec; 3 | var fs = require('fs-extra'); 4 | var git = require('gulp-git'); 5 | var syncToGitHub = require('sync-to-github'); 6 | var gulp = require('gulp'); 7 | var mocha = require('gulp-mocha'); 8 | var rename = require('gulp-rename'); 9 | var tagVersion = require('gulp-tag-version'); 10 | var template = require('gulp-template'); 11 | var util = require('util'); 12 | var yaml = require('js-yaml'); 13 | var resource = require('./src/resource'); 14 | var helpers = require('./src/helpers'); 15 | var _ = require('lodash'); 16 | var Bluebird = require('bluebird'); 17 | var GitHubApi = require('github'); 18 | var dateFormat = require('dateformat'); 19 | 20 | /** 21 | * Paths 22 | */ 23 | var test = 'test/**/*'; 24 | 25 | /** 26 | * Environment 27 | */ 28 | var languages = { 29 | js: { 30 | repo: 'Asana/node-asana', 31 | outputPath: 'lib/resources/gen', 32 | preserveRepoFiles: false 33 | }, 34 | php: { 35 | repo: 'Asana/php-asana', 36 | outputPath: 'src/Asana/Resources/Gen', 37 | preserveRepoFiles: false 38 | }, 39 | java: { 40 | repo: 'Asana/java-asana', 41 | outputPath: 'src/main/java/com/asana/resources/gen', 42 | preserveRepoFiles: false 43 | }, 44 | python: { 45 | repo: 'Asana/python-asana', 46 | outputPath: 'asana/resources/gen', 47 | // Keep the __init__.py file there 48 | preserveRepoFiles: true 49 | }, 50 | ruby: { 51 | repo: 'Asana/ruby-asana', 52 | outputPath: 'lib/asana/resources', 53 | preserveRepoFiles: false, 54 | skip: ['event'] 55 | }, 56 | api_explorer: { 57 | repo: 'Asana/api-explorer', 58 | outputPath: 'src/resources/gen', 59 | preserveRepoFiles: false 60 | }, 61 | api_reference: { 62 | repo: 'Asana/asanastatic', 63 | outputPath: '_content/developers/02-api-reference', 64 | // Keep the other markdown pages and metadata there 65 | preserveRepoFiles: true 66 | } 67 | }; 68 | 69 | var paths = { 70 | dist: function(lang) { 71 | return 'dist/' + lang; 72 | }, 73 | repoOutputRelative: function(lang) { 74 | return languages[lang].outputPath; 75 | } 76 | }; 77 | 78 | function readPackage(filename) { 79 | return fs.readJSONSync(filename || 'package.json'); 80 | } 81 | 82 | function writePackage(filename, data) { 83 | return fs.writeJSONSync(filename || 'package.json', data); 84 | } 85 | 86 | /** 87 | * High-level tasks 88 | */ 89 | 90 | // Build all languages 91 | gulp.task('build', ['test'].concat(Object.keys(languages).map(function(lang) { 92 | return 'build-' + lang; 93 | }))); 94 | 95 | // Deploy languages 96 | gulp.task('deploy', ['ensure-git-clean', 'build'].concat(Object.keys(languages).map(function(lang) { 97 | return 'deploy-' + lang; 98 | }))); 99 | 100 | 101 | // Store a single timestamp representing right now. 102 | var nowTimestamp = dateFormat('yyyymmdd-HHMMss'); 103 | 104 | 105 | /** 106 | * Generate build and deploy rules for each language 107 | */ 108 | var resourceNames = resource.names(); 109 | Object.keys(languages).forEach(function(lang) { 110 | var config = languages[lang]; 111 | var token = process.env.ASANA_GITHUB_TOKEN || null; 112 | var isProd = (process.env.PROD == '1'); 113 | 114 | function echoAndExec(command, options) { 115 | if (process.env.GULP_DEBUG) { 116 | console.log('+ ' + arguments[0]); 117 | } 118 | 119 | return new Bluebird(function(resolve, reject) { 120 | return exec(command, options, function(err, stdout, stderr) { 121 | if (err) { 122 | reject(err); 123 | } else { 124 | resolve(stdout); 125 | } 126 | }); 127 | }); 128 | } 129 | 130 | function resTaskName(resourceName) { 131 | return 'build-' + lang + '-' + resourceName; 132 | } 133 | 134 | /** 135 | * Clean work area for target repo 136 | */ 137 | Object.keys(languages).forEach(function(lang) { 138 | gulp.task('clean-' + lang, function() { 139 | fs.removeSync(paths.dist(lang)); 140 | }); 141 | }); 142 | 143 | /** 144 | * Build rules, per resource. 145 | */ 146 | var resourcesToSkip = config.skip || []; 147 | var resourcesToBuild = _.difference(resourceNames, resourcesToSkip); 148 | resourcesToBuild.forEach(function(resourceName) { 149 | gulp.task(resTaskName(resourceName), function() { 150 | // Support only local templates 151 | var templatePath = 'src/templates/' + lang; 152 | var resourceTemplateInfo = require('./' + templatePath).resource; 153 | 154 | // Load the resource yaml into a variable 155 | var resourceInstance = resource.load(resourceName); 156 | var templateHelpers = helpers(lang); 157 | templateHelpers.resources = resourceNames; 158 | // Load the resource file 159 | return gulp.src(templatePath + '/' + resourceTemplateInfo.template) 160 | .pipe( //Pipe it through a templating path with the language-specific helpers 161 | template(resourceInstance, { 162 | imports: templateHelpers, 163 | variable: 'resource' 164 | })) 165 | .pipe( //Pipe it through a file renaming step, i.e. resource.ejs becomes project.rb 166 | rename(resourceTemplateInfo.filename(resourceInstance, templateHelpers))) 167 | .pipe( //Pipe it to the output destination 168 | gulp.dest(paths.dist(lang))); 169 | }); 170 | }); 171 | gulp.task( 172 | 'build-' + lang, 173 | resourcesToBuild.map(resTaskName)); 174 | 175 | /** 176 | * Deploy 177 | */ 178 | 179 | /** 180 | * @returns {Promise} The commit message to provide for a deployment. 181 | */ 182 | function createCommitMessage(user) { 183 | var version = readPackage().version; 184 | var revParse = Bluebird.promisify(git.revParse, git); 185 | 186 | var githubUserName = user.login; 187 | return revParse({args: '--abbrev-ref HEAD'}).then(function(branchName) { 188 | return revParse({args: '--short HEAD'}).then(function(commitHash) { 189 | var commitDesc = branchName.trim() ? 190 | util.format("%s/%s", commitHash, branchName.trim()) : 191 | commitHash; 192 | return util.format( 193 | "Deploy from asana-api-meta v%s (%s) by %s", 194 | version, commitDesc, githubUserName); 195 | }); 196 | }); 197 | } 198 | 199 | function getGitHubUser() { 200 | var github = githubClient(token); 201 | var getUser = Bluebird.promisify(github.user.get, github.user); 202 | return getUser({}); 203 | } 204 | 205 | function deployWithGithubApi() { 206 | return getGitHubUser().then(function(user) { 207 | var branchName = isProd ? 208 | 'api-meta-incoming' : 209 | (user.login + '-' + nowTimestamp); 210 | return createCommitMessage(user).then(function(commitMessage) { 211 | var repoParts = config.repo.split('/'); 212 | return syncToGitHub({ 213 | oauthToken: token, 214 | user: repoParts[0], 215 | repo: repoParts[1], 216 | localPath: paths.dist(lang), 217 | repoPath: paths.repoOutputRelative(lang), 218 | branch: branchName, 219 | baseBranch: 'master', 220 | createBranch: true, 221 | message: commitMessage, 222 | preserveRepoFiles: !!config.preserveRepoFiles, 223 | createPullRequest: isProd, 224 | debug: !!process.env.GULP_DEBUG 225 | }); 226 | }); 227 | }); 228 | } 229 | 230 | function githubClient(token) { 231 | var github = new GitHubApi({ 232 | version: '3.0.0', 233 | protocol: 'https', 234 | host: 'api.github.com' 235 | }); 236 | github.authenticate({ 237 | type: 'oauth', 238 | token: token 239 | }); 240 | return github; 241 | } 242 | 243 | gulp.task( 244 | 'deploy-' + lang, 245 | ['ensure-git-clean', 'build-' + lang], 246 | deployWithGithubApi); 247 | }); 248 | 249 | 250 | /** 251 | * Bumping version number and tagging the repository with it. 252 | * Please read http://semver.org/ 253 | * 254 | * You can use the commands 255 | * 256 | * gulp bump-patch # makes v0.1.0 → v0.1.1 257 | * gulp bump-feature # makes v0.1.1 → v0.2.0 258 | * gulp bump-release # makes v0.2.1 → v1.0.0 259 | * 260 | * To bump the version numbers accordingly after you did a patch, 261 | * introduced a feature or made a backwards-incompatible release. 262 | */ 263 | function bumpVersion(importance) { 264 | return gulp.src(['./package.json']) 265 | .pipe(bump({type: importance})) 266 | .pipe(gulp.dest('./')) 267 | .pipe(git.commit('bump package version')) 268 | .pipe(tagVersion()); 269 | } 270 | gulp.task('bump-patch', ['ensure-git-clean'], function() { 271 | return bumpVersion('patch'); 272 | }); 273 | gulp.task('bump-feature', ['ensure-git-clean'], function() { 274 | return bumpVersion('minor'); 275 | }); 276 | gulp.task('bump-release', ['ensure-git-clean'], function() { 277 | return bumpVersion('major'); 278 | }); 279 | 280 | /** 281 | * Ensure that the git working directory is clean. 282 | */ 283 | gulp.task('ensure-git-clean', function() { 284 | git.status(function(err, out) { 285 | if (err) { throw err; } 286 | if (!/working tree clean/.exec(out)) { 287 | // Working directory must be clean for some operations. 288 | // For bumping the version, this prevents accidental commits of 289 | // unintended or partial changes. 290 | // For deployment, this ensures that the deploy is tagged with a commit 291 | // and that can be used to reference the exact state of the repo (if we 292 | // allowed changes then the commit would not tell us what the code 293 | // actually looked like). 294 | throw new Error('Git working directory not clean, will not proceed.'); 295 | } 296 | }); 297 | }); 298 | 299 | /** 300 | * Tests the code with mocha. 301 | */ 302 | gulp.task('test', function(callback) { 303 | gulp.src(test) 304 | .pipe(mocha({ 305 | reporter: process.env.TRAVIS ? 'spec' : 'nyan' 306 | })); 307 | }); 308 | 309 | 310 | /** 311 | * Copy the documents for api-reference to a sibling static site installation. 312 | * TODO: it appears that the convention might be to do all of those repos in 313 | * subdirs (as opposed to sibling dirs), based on paths.repoOutputRelative 314 | */ 315 | 316 | gulp.task('local-copy-api-reference', ['build-api_reference'], function(callback) { 317 | fs.copySync(paths.dist('api_reference'), "../../vagrant-php7/asanastatic/" + paths.repoOutputRelative('api_reference')); 318 | callback() 319 | }); 320 | 321 | /** 322 | * Setup gulp to watch the metadata and depoly to a working copy 323 | * of the static site. The only assumptions are that asana-api-meta 324 | * and the static site repo are in the same directory. 325 | */ 326 | gulp.task('watch-documents', function(callback) { 327 | gulp.watch("src/**/*.{js,yaml,ejs}", ['local-copy-api-reference']); 328 | }); 329 | 330 | gulp.task('default', ['build', 'local-copy-api-reference']); 331 | -------------------------------------------------------------------------------- /src/resources/webhook.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | !include ../includes.yaml 3 | name: webhook 4 | comment: | 5 | Webhooks allow an application to be notified of changes. This is in addition 6 | to the ability to fetch those changes directly as 7 | [Events](/developers/api-reference/events) - in fact, Webhooks are just a way 8 | to receive Events via HTTP POST at the time they occur instead of polling for 9 | them. For services accessible via HTTP this is often vastly more convenient, 10 | and if events are not too frequent can be significantly more efficient. 11 | 12 | In both cases, however, changes are represented as Event objects - refer to 13 | the [Events documentation](/developers/api-reference/events) for more 14 | information on what data these events contain. 15 | 16 | **NOTE:** While Webhooks send arrays of Event objects to their target, the 17 | Event objects themselves contain *only IDs*, rather than the actual resource 18 | they are referencing. So while a normal event you receive via GET /events 19 | would look like this: 20 | 21 | {\ 22 | "resource": {\ 23 | "id": 1337,\ 24 | "resource_type": "task",\ 25 | "name": "My Task"\ 26 | },\ 27 | "parent": null,\ 28 | "created_at": "2013-08-21T18:20:37.972Z",\ 29 | "user": {\ 30 | "id": 1123,\ 31 | "resource_type": "user",\ 32 | "name": "Tom Bizarro"\ 33 | },\ 34 | "action": "changed",\ 35 | "type": "task"\ 36 | } 37 | 38 | In a Webhook payload you would instead receive this: 39 | 40 | {\ 41 | "resource": 1337,\ 42 | "parent": null,\ 43 | "created_at": "2013-08-21T18:20:37.972Z",\ 44 | "user": 1123,\ 45 | "action": "changed",\ 46 | "type": "task"\ 47 | } 48 | 49 | Webhooks themselves contain only the information necessary to deliver the 50 | events to the desired target as they are generated. 51 | properties: 52 | 53 | - name: id 54 | <<: *PropType.Id 55 | comment: | 56 | Globally unique ID of the webhook. 57 | **Note: This field is under active migration to the [`gid` field](#field-gid)--please see our [blog post](/developers/documentation/getting-started/deprecations) for more information.** 58 | 59 | - name: gid 60 | <<: *PropType.Gid 61 | comment: | 62 | Globally unique ID of the webhook. 63 | 64 | - name: resource_type 65 | <<: *PropType.ResourceType 66 | comment: | 67 | The resource type of this resource. The value for this resource is always `webhook`. 68 | example_values: 69 | - '"webhook"' 70 | values: 71 | - name: webhook 72 | comment: A webhook resource type. 73 | 74 | - name: resource 75 | <<: *PropType.Task 76 | access: Read-only 77 | comment: | 78 | The resource the webhook is subscribed to. 79 | 80 | - name: target 81 | <<: *PropType.Target 82 | access: Read-only 83 | comment: | 84 | The URL to receive the HTTP POST. 85 | 86 | - name: active 87 | <<: *PropType.Bool 88 | comment: | 89 | If true, the webhook will send events - if false it is considered 90 | inactive and will not generate events. 91 | 92 | - name: created_at 93 | <<: *PropType.DateTime 94 | access: Read-only 95 | comment: | 96 | The timestamp when the webhook was created. 97 | 98 | - name: last_success_at 99 | <<: *PropType.DateTime 100 | access: Read-only 101 | comment: | 102 | The timestamp when the webhook last successfully sent an event to the 103 | target. 104 | 105 | - name: last_failure_at 106 | <<: *PropType.DateTime 107 | access: Read-only 108 | comment: | 109 | The timestamp when the webhook last received an error when sending an 110 | event to the target. 111 | 112 | - name: last_failure_content 113 | <<: *PropType.FailureContent 114 | access: Read-only 115 | comment: | 116 | The contents of the last error response sent to the webhook when 117 | attempting to deliver events to the target. 118 | 119 | action_classes: 120 | - name: Create a webhook 121 | url: create 122 | - name: Get webhooks 123 | url: get 124 | - name: Get webhook 125 | url: get-single 126 | - name: Receiving webhook events 127 | url: receive 128 | comment: | 129 | Because multiple events often happen in short succession, a webhook 130 | payload is designed to be able to transmit multiple events at once. The 131 | exact model of events is described in the 132 | [Events documentation](/developers/api-reference/events). 133 | 134 | The HTTP POST that the target receives contains: 135 | 136 | * An `X-Hook-Signature` header, which allows verifying that the payload 137 | is genuine. The signature is a SHA256 HMAC using the shared secret 138 | (transmitted during the handshake) of the request body. Verification is 139 | **strongly recommended**, as it would otherwise be possible for an 140 | attacker to POST a malicious payload to the same endpoint. If the target 141 | endpoint can be kept secret this risk is mitigated somewhat, of course.\ 142 | * A JSON body with a single key, `events`, containing an array of the 143 | events that have occurred since the last webhook delivery. Note that this 144 | list may be empty, as periodically we may send a "heartbeat" webhook to 145 | verify that the endpoint is available. 146 | 147 | Note that events are "skinny" - we expect consumers who desire syncing 148 | data to make additional calls to the API to retrieve the latest state. 149 | Because the data may have already changed by the time we send the event, 150 | it would be misleading to send a snapshot of the data along with the 151 | event. 152 | 153 | **Example** 154 | 155 | # Request to your server\ 156 | POST /receive-webhook/7654\ 157 | X-Hook-Signature: 1d6207f8818f063890758a32d3833914754ba788cb8993e644701bac7257f59e 158 | 159 | {\ 160 | "events": [\ 161 | {\ 162 | "action": "changed",\ 163 | "created_at": "2013-08-21T18:20:37.972Z",\ 164 | "parent": null,\ 165 | "resource": 1337,\ 166 | "type": "task",\ 167 | "user": 1123\ 168 | },\ 169 | {\ 170 | "action": "changed",\ 171 | "created_at": "2013-08-21T18:22:45.421Z",\ 172 | "parent": null,\ 173 | "resource": 1338,\ 174 | "type": "task",\ 175 | "user": 1428\ 176 | }\ 177 | ]\ 178 | } 179 | 180 | - name: Error handling and retry 181 | url: retry 182 | comment: | 183 | If we attempt to send a webhook payload and we receive an error status 184 | code, or the request times out, we will retry delivery with exponential 185 | backoff. In general, if your servers are not available for an hour, you 186 | can expect it to take no longer than approximately an hour after they 187 | come back before the paused delivery resumes. However, if we are unable 188 | to deliver a message for 24 hours the webhook will be deactivated. 189 | - name: Delete a webhook 190 | url: delete 191 | actions: 192 | 193 | - name: create 194 | class: create 195 | method: POST 196 | path: "/webhooks" 197 | params: 198 | - name: resource 199 | <<: *Param.Id 200 | required: true 201 | explicit: true 202 | comment: | 203 | A resource ID to subscribe to. The resource can be a task or project. 204 | - name: target 205 | <<: *Param.Target 206 | required: true 207 | explicit: true 208 | comment: | 209 | The URL to receive the HTTP POST. 210 | comment: | 211 | Establishing a webhook is a two-part process. First, a simple HTTP POST 212 | similar to any other resource creation. Since you could have multiple 213 | webhooks we recommend specifying a unique local id for each target. 214 | 215 | Next comes the confirmation handshake. When a webhook is created, we will 216 | send a test POST to the `target` with an `X-Hook-Secret` header as 217 | described in the 218 | [Resthooks Security documentation](http://resthooks.org/docs/security/). 219 | The target must respond with a `200 OK` and a matching `X-Hook-Secret` 220 | header to confirm that this webhook subscription is indeed expected. 221 | 222 | If you do not acknowledge the webhook's confirmation handshake it will 223 | fail to setup, and you will receive an error in response to your attempt 224 | to create it. This means you need to be able to receive and complete the 225 | webhook *while* the POST request is in-flight. 226 | footer: | 227 | **Example** 228 | 229 | # Request\ 230 | curl -H "Authorization: Bearer " \\ 231 | -X POST https://app.asana.com/api/1.0/webhooks \\ 232 | -d "resource=8675309" \\ 233 | -d "target=https://example.com/receive-webhook/7654" 234 | 235 | # Handshake sent to https://example.com/\ 236 | POST /receive-webhook/7654\ 237 | X-Hook-Secret: b537207f20cbfa02357cf448134da559e8bd39d61597dcd5631b8012eae53e81 238 | 239 | # Handshake response sent by example.com\ 240 | HTTP/1.1 200\ 241 | X-Hook-Secret: b537207f20cbfa02357cf448134da559e8bd39d61597dcd5631b8012eae53e81 242 | 243 | # Response\ 244 | HTTP/1.1 201\ 245 | {\ 246 | "data": {\ 247 | "id": 43214,\ 248 | "resource_type": "webhook",\ 249 | "resource": {\ 250 | "id": 8675309,\ 251 | "gid": "8675309",\ 252 | "resource_type": "project",\ 253 | "name": "Bugs"\ 254 | },\ 255 | "target": "https://example.com/receive-webhook/7654",\ 256 | "active": true,\ 257 | "created_at": "2018-10-12T19:06:29.993Z",\ 258 | "last_success_at": null,\ 259 | "last_failure_at": null,\ 260 | "last_failure_content": null\ 261 | }\ 262 | } 263 | - name: getAll 264 | class: get 265 | method: GET 266 | path: "/webhooks" 267 | collection: true 268 | params: 269 | - name: workspace 270 | <<: *Param.WorkspaceId 271 | required: true 272 | explicit: true 273 | comment: | 274 | The workspace to query for webhooks in. 275 | - name: resource 276 | <<: *Param.Id 277 | comment: | 278 | Only return webhooks for the given resource. 279 | comment: | 280 | Returns the compact representation of all webhooks your app has 281 | registered for the authenticated user in the given workspace. 282 | - name: getById 283 | class: get-single 284 | method: GET 285 | path: "/webhooks/%s" 286 | params: 287 | - name: webhook 288 | <<: *Param.Id 289 | required: true 290 | explicit: true 291 | comment: | 292 | The webhook to get. 293 | comment: | 294 | Returns the full record for the given webhook. 295 | - name: deleteById 296 | class: delete 297 | method: DELETE 298 | path: "/webhooks/%s" 299 | params: 300 | - name: webhook 301 | <<: *Param.Id 302 | required: true 303 | explicit: true 304 | comment: | 305 | The webhook to delete. 306 | comment: | 307 | This method permanently removes a webhook. Note that it may be possible 308 | to receive a request that was already in flight after deleting the 309 | webhook, but no further requests will be issued. 310 | -------------------------------------------------------------------------------- /src/resources/portfolio.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # See `user.yaml` for more docs on these yaml files. 3 | !include ../includes.yaml 4 | name: portfolio 5 | comment: | 6 | A _portfolio_ gives a high-level overview of the status of multiple 7 | initiatives in Asana. Portfolios provide a dashboard overview of the state 8 | of multiple items, including a progress report and the most recent 9 | [project status](/developers/api-reference/project_statuses) update. 10 | 11 | Portfolios have some restrictions on size. Each portfolio has a maximum of 250 12 | items and, like projects, a maximum of 20 custom fields. 13 | 14 | notes: 15 | - | 16 | 17 | properties: 18 | 19 | - name: id 20 | <<: *PropType.Id 21 | comment: | 22 | Globally unique ID of the portfolio. 23 | **Note: This field is under active migration to the [`gid` field](#field-gid)--please see our [blog post](/developers/documentation/getting-started/deprecations) for more information.** 24 | 25 | - name: gid 26 | <<: *PropType.Gid 27 | comment: | 28 | Globally unique ID of the portfolio. 29 | 30 | - name: resource_type 31 | <<: *PropType.ResourceType 32 | comment: | 33 | The resource type of this resource. The value for this resource is always `portfolio`. 34 | example_values: 35 | - '"portfolio"' 36 | values: 37 | - name: portfolio 38 | comment: A portfolio resource type. 39 | 40 | - name: name 41 | <<: *PropType.PortfolioName 42 | comment: | 43 | Name of the portfolio. 44 | 45 | - name: owner 46 | <<: *PropType.PortfolioOwner 47 | access: Read-only 48 | comment: | 49 | The current owner of the portfolio. Cannot be null. 50 | 51 | - name: created_at 52 | <<: *PropType.DateTime 53 | access: Read-only 54 | comment: | 55 | The time at which this portfolio was created. 56 | 57 | - name: created_by 58 | <<: *PropType.User 59 | access: Read-only 60 | comment: | 61 | The user that created the portfolio. 62 | 63 | - name: custom_field_settings 64 | <<: *PropType.PortfolioCustomFieldSettingsCompactArray 65 | access: Read-only 66 | comment: | 67 | Array of custom field settings applied to the portfolio. 68 | 69 | - name: color 70 | <<: *PropType.PortfolioColor 71 | comment: | 72 | Must be either `none` or one of: `dark-pink`, `dark-green`, `dark-blue`, 73 | `dark-red`, `dark-teal`, `dark-brown`, `dark-orange`, `dark-purple`, 74 | `dark-warm-gray`, `light-pink`, `light-green`, `light-blue`, `light-red`, 75 | `light-teal`, `light-yellow`, `light-orange`, `light-purple`, 76 | `light-warm-gray`. 77 | 78 | - name: workspace 79 | <<: *PropType.Workspace 80 | comment: | 81 | The workspace or organization that the portfolio belongs to. 82 | 83 | - name: members 84 | <<: *PropType.UserArray 85 | comment: | 86 | Members of the portfolio. 87 | 88 | 89 | action_classes: 90 | - name: Create a portfolio 91 | url: create 92 | - name: Get a portfolio 93 | url: get-single 94 | - name: Update a portfolio 95 | url: update 96 | - name: Delete a portfolio 97 | url: delete 98 | - name: Query for portfolios 99 | url: query 100 | - name: Items in a portfolio 101 | url: items 102 | - name: Work with portfolio memberships 103 | url: members 104 | - name: Custom fields on a portfolio 105 | url: custom-field-settings 106 | 107 | actions: 108 | 109 | # Create, Retrieve, Update, Delete 110 | 111 | - name: create 112 | class: create 113 | method: POST 114 | path: "/portfolios" 115 | params: 116 | - name: workspace 117 | <<: *Param.WorkspaceGid 118 | required: true 119 | comment: The workspace or organization in which to create the portfolio. 120 | - name: name 121 | required: true 122 | <<: *Param.PortfolioName 123 | comment: The name of the newly-created portfolio 124 | - name: color 125 | required: false 126 | <<: *Param.PortfolioColor 127 | comment: An optional color for the portfolio 128 | comment: | 129 | Creates a new portfolio in the given workspace with the supplied name. 130 | 131 | Note that portfolios created in the Asana UI may have some state 132 | (like the "Priority" custom field) which is automatically added to the 133 | portfolio when it is created. Portfolios created via our API will **not** 134 | be created with the same initial state to allow integrations to create 135 | their own starting state on a portfolio. 136 | 137 | - name: findById 138 | class: get-single 139 | method: GET 140 | path: "/portfolios/%s" 141 | params: 142 | - name: portfolio 143 | <<: *Param.PortfolioGid 144 | required: true 145 | comment: The portfolio to get. 146 | comment: | 147 | Returns the complete record for a single portfolio. 148 | 149 | - name: update 150 | class: update 151 | method: PUT 152 | path: "/portfolios/%s" 153 | params: 154 | - name: portfolio 155 | <<: *Param.PortfolioGid 156 | required: true 157 | comment: The portfolio to update. 158 | comment: | 159 | An existing portfolio can be updated by making a PUT request on the 160 | URL for that portfolio. Only the fields provided in the `data` block will be 161 | updated; any unspecified fields will remain unchanged. 162 | 163 | Returns the complete updated portfolio record. 164 | 165 | - name: delete 166 | class: delete 167 | method: DELETE 168 | path: "/portfolios/%s" 169 | params: 170 | - name: portfolio 171 | <<: *Param.PortfolioGid 172 | required: true 173 | comment: The portfolio to delete. 174 | comment: | 175 | An existing portfolio can be deleted by making a DELETE request 176 | on the URL for that portfolio. 177 | 178 | Returns an empty data record. 179 | 180 | - name: findAll 181 | class: query 182 | method: GET 183 | path: "/portfolios" 184 | collection: true 185 | comment: | 186 | Returns a list of the portfolios in compact representation that are owned 187 | by the current API user. 188 | params: 189 | - name: workspace 190 | <<: *Param.WorkspaceGid 191 | required: true 192 | comment: The workspace or organization to filter portfolios on. 193 | - name: owner 194 | <<: *Param.User 195 | required: true 196 | comment: | 197 | The user who owns the portfolio. Currently, API users can only get a 198 | list of portfolios that they themselves own. 199 | 200 | - name: getItems 201 | class: items 202 | method: GET 203 | path: "/portfolios/%s/items" 204 | collection: true 205 | comment: | 206 | Get a list of the items in compact form in a portfolio. 207 | params: 208 | - name: portfolio 209 | <<: *Param.PortfolioGid 210 | required: true 211 | comment: The portfolio from which to get the list of items. 212 | 213 | - name: addItem 214 | class: items 215 | method: POST 216 | path: "/portfolios/%s/addItem" 217 | comment: | 218 | Add an item to a portfolio. 219 | 220 | Returns an empty data block. 221 | 222 | params: 223 | - name: portfolio 224 | <<: *Param.PortfolioGid 225 | required: true 226 | comment: The portfolio to which to add an item. 227 | - name: item 228 | <<: *Param.ProjectGid 229 | required: true 230 | comment: The item to add to the portfolio. 231 | - name: insert_before 232 | <<: *Param.ProjectGid 233 | comment: | 234 | An id of an item in this portfolio. The new item will be added before the one specified here. 235 | `insert_before` and `insert_after` parameters cannot both be specified. 236 | - name: insert_after 237 | <<: *Param.ProjectGid 238 | comment: | 239 | An id of an item in this portfolio. The new item will be added after the one specified here. 240 | `insert_before` and `insert_after` parameters cannot both be specified. 241 | 242 | - name: removeItem 243 | class: items 244 | method: POST 245 | path: "/portfolios/%s/removeItem" 246 | comment: | 247 | Remove an item to a portfolio. 248 | 249 | Returns an empty data block. 250 | 251 | params: 252 | - name: portfolio 253 | <<: *Param.PortfolioGid 254 | required: true 255 | comment: The portfolio from which to remove the item. 256 | - name: item 257 | <<: *Param.ProjectGid 258 | required: true 259 | comment: The item to remove from the portfolio. 260 | 261 | - name: addMembers 262 | class: members 263 | method: POST 264 | path: "/portfolios/%s/addMembers" 265 | params: 266 | - name: portfolio 267 | <<: *Param.ProjectGid 268 | required: true 269 | comment: The portfolio to add members to. 270 | - name: members 271 | <<: *Param.GidArray 272 | required: true 273 | comment: An array of user ids. 274 | comment: | 275 | Adds the specified list of users as members of the portfolio. Returns the updated portfolio record. 276 | 277 | - name: removeMembers 278 | class: members 279 | method: POST 280 | path: "/portfolios/%s/removeMembers" 281 | params: 282 | - name: portfolio 283 | <<: *Param.PortfolioGid 284 | required: true 285 | comment: The portfolio to remove members from. 286 | - name: members 287 | <<: *Param.GidArray 288 | required: true 289 | comment: An array of user ids. 290 | comment: | 291 | Removes the specified list of members from the portfolio. Returns the updated portfolio record. 292 | 293 | - name: customFieldSettings 294 | class: custom-field-settings 295 | method: GET 296 | path: "/portfolios/%s/custom_field_settings" 297 | collection: true 298 | comment: | 299 | Get the custom field settings on a portfolio. 300 | params: 301 | - name: portfolio 302 | <<: *Param.PortfolioGid 303 | required: true 304 | comment: The portfolio from which to get the custom field settings. 305 | 306 | - name: addCustomFieldSetting 307 | class: custom-field-settings 308 | method: POST 309 | path: "/portfolios/%s/addCustomFieldSetting" 310 | comment: | 311 | Create a new custom field setting on the portfolio. Returns the full 312 | record for the new custom field setting. 313 | params: 314 | - name: portfolio 315 | <<: *Param.PortfolioGid 316 | required: true 317 | comment: The portfolio onto which to add the custom field. 318 | - name: custom_field 319 | <<: *Param.CustomFieldGid 320 | required: true 321 | comment: The id of the custom field to add to the portfolio. 322 | - name: is_important 323 | <<: *Param.Bool 324 | comment: | 325 | Whether this field should be considered important to this portfolio (for instance, to display in the list view of items in the portfolio). 326 | - name: insert_before 327 | <<: *Param.CustomFieldSettingsGid 328 | comment: | 329 | An id of a custom field setting on this portfolio. The new custom field setting will be added before this one. 330 | `insert_before` and `insert_after` parameters cannot both be specified. 331 | - name: insert_after 332 | <<: *Param.CustomFieldSettingsGid 333 | comment: | 334 | An id of a custom field setting on this portfolio. The new custom field setting will be added after this one. 335 | `insert_before` and `insert_after` parameters cannot both be specified. 336 | 337 | - name: removeCustomFieldSetting 338 | class: custom-field-settings 339 | method: POST 340 | path: "/portfolios/%s/removeCustomFieldSetting" 341 | comment: | 342 | Remove a custom field setting on the portfolio. Returns an empty data 343 | block. 344 | params: 345 | - name: portfolio 346 | <<: *Param.PortfolioGid 347 | required: true 348 | comment: The portfolio from which to remove the custom field. 349 | - name: custom_field 350 | required: true 351 | <<: *Param.CustomFieldGid 352 | comment: The id of the custom field to remove from this portfolio. 353 | -------------------------------------------------------------------------------- /src/helpers.js: -------------------------------------------------------------------------------- 1 | var inflect = require('inflect'); 2 | var util = require('util'); 3 | var path = require('path') 4 | var _ = require('lodash'); 5 | var fs = require('fs') 6 | var yaml = require('js-yaml'); 7 | 8 | /** 9 | * Helpers for code-generation templates 10 | */ 11 | 12 | function _repeat(char, times) { 13 | var s = ''; 14 | for (var i = 0; i < times; i++) { 15 | s += char; 16 | } 17 | return s; 18 | } 19 | 20 | /** 21 | * Wrap the interior of a multi-line comment at the 80-column boundary. 22 | * 23 | * @param {String} text 24 | * @param {String?} prefix 25 | * @param {Number?} maxChars 26 | * @returns {*} 27 | */ 28 | function wrapComment(text, prefix, maxChars) { 29 | // TODO: actually wrap :) 30 | prefix = prefix || ""; 31 | maxChars = maxChars || 78; 32 | return prefixLines(text, prefix); 33 | } 34 | 35 | function prefixLines(text, prefix, skipFirst) { 36 | return (skipFirst ? "" : prefix) + text.trim().replace(/\n/g, "\n" + prefix); 37 | } 38 | 39 | function wrapStarComment(text, indent) { 40 | return wrapComment(text, _repeat(' ', indent || 0) + ' * '); 41 | } 42 | 43 | function wrapHashComment(text, indent) { 44 | return wrapComment(text, _repeat(' ', indent || 0) + '# '); 45 | } 46 | 47 | function typeNameTranslator(lang) { 48 | return ({ 49 | js: function(name) { 50 | return ({ 51 | Id: 'String', 52 | Enum: 'String' 53 | })[name] || name; 54 | }, 55 | java: function(name) { 56 | return ({ 57 | Id: 'String', 58 | Enum: 'String' 59 | })[name] || name; 60 | } 61 | })[lang] || function(x) { return x; }; 62 | } 63 | 64 | function paramsForAction(action) { 65 | // Figure out how many params will be consumed by the path and put the 66 | // first N required params there - the rest go in options. 67 | var numPathParams = (action.path.match(/%/g) || []).length; 68 | var results = { 69 | pathParams: [], 70 | explicitNonPathParams: [], 71 | optionParams: [] 72 | }; 73 | if (action.params) { 74 | action.params.forEach(function(param, index) { 75 | if (param.required && results.pathParams.length < numPathParams) { 76 | results.pathParams.push(param); 77 | } else if (param.explicit) { 78 | results.explicitNonPathParams.push(param); 79 | } else { 80 | results.optionParams.push(param); 81 | } 82 | }); 83 | } 84 | return results; 85 | } 86 | 87 | // Removes line breaks but preserves paragraphs (double newlines). 88 | // Also reserves line breaks denoted with an optional delimiter. 89 | // TODO: make this un-hacky 90 | function removeLineBreaks(text, opt_paragraph_delim) { 91 | var paragraph_delim = opt_paragraph_delim || "\n\n"; 92 | text = text.replace(/\\\n/gm, "XX"); 93 | text = text.replace(/\n\n/gm, "CC"); 94 | text = text.replace(/\r\n|\n|\r/gm, " "); 95 | text = text.replace(/XX/g, "\n"); 96 | return text.replace(/CC/g, paragraph_delim).trim(); 97 | } 98 | 99 | // Strip a string of leading/trailing whitespace 100 | // unlike removeLineBreaks, this doesn't affect anything inside the text. 101 | function stripWhitespace(text) { 102 | return text.replace(/^\s+/, '').replace(/\s+$/, '') 103 | } 104 | 105 | function idIfyParamName(name) { 106 | return name + "_gid"; 107 | } 108 | 109 | function genericPath(action, pathParams) { 110 | var path = action.path; 111 | _.forEach(pathParams, function(pathParam) { 112 | path = path.replace(/%./, "{" + idIfyParamName(pathParam.name) + "}"); 113 | }); 114 | return path; 115 | } 116 | 117 | function examplesForResource(resource) { 118 | var yamlFile = fs.readFileSync(path.join(__dirname, './templates/examples.yaml'), 'utf8'); 119 | var examples = yaml.load(yamlFile); 120 | return examples[resource]; 121 | } 122 | 123 | // This is for "action class" as in "action_classes" which is, in this context, 124 | // "class" as in css class. Basically, we can have a section of only text that 125 | // falls under a blue header that can be linked to. 126 | function curlExamplesForKeys(keys, resource_examples) { 127 | var key_examples = _.filter(resource_examples, function(example) { 128 | if (! example.key) return false; 129 | var index = _.findIndex(keys, function(key){ 130 | return key === example.key 131 | }) 132 | if(index != -1) { 133 | return true; 134 | } 135 | return false 136 | }); 137 | return buildCurlExamples(key_examples); 138 | } 139 | 140 | // Note: this is "action" as in "endpoint description"; `GET /tasks` for example. 141 | function curlExamplesForAction(action, resource_examples) { 142 | var action_examples = _.filter(resource_examples, function(example) { 143 | //TODO: this is a hack, simply to exclude selection-by-key vs selection-by-action/endpoint 144 | if (example.key) return false; 145 | var regex = "^" + action.path.replace(/%s/g, "[^\/]+").replace(/\//g, "\\/") + "[^\\/]*$"; 146 | match = (example.method === action.method.toLowerCase() && example.endpoint.match(regex)); 147 | return match 148 | 149 | }); 150 | return buildCurlExamples(action_examples); 151 | } 152 | 153 | function buildCurlExamples(examples) { 154 | var curlExamples = []; 155 | _.forEach(examples, function(example) { 156 | var request = 'curl'; 157 | if (example.method === 'put') { 158 | request += ' --request PUT'; 159 | } else if (example.method === 'delete') { 160 | request += ' --request DELETE'; 161 | } 162 | request += ' -H "Authorization: Bearer "'; 163 | var url = 'https://app.asana.com/api/1.0' + example.endpoint; 164 | var data = []; 165 | if (example.request_data) { 166 | _.forEach(example.request_data, function(value, param_name) { 167 | var line; 168 | if (Array.isArray(value)) { // exception for array types because of curl weirdness 169 | line = '--data-urlencode "' + param_name + '[0]=' + value[0] + '"'; 170 | } else if (param_name === 'file') { // exception for files 171 | line = '--form "' + param_name + "=" + value + '"'; 172 | } else { 173 | line = '--data-urlencode "' + param_name + "=" + value + '"'; 174 | } 175 | data.push(line); 176 | }) 177 | } 178 | var response_status = ""; 179 | var response = {}; 180 | _.forEach(example.response, function(value, field_name) { 181 | if (field_name === 'status') { 182 | response_status = "HTTP/1.1 " + example.response.status; 183 | } else { 184 | response[field_name] = value; 185 | } 186 | }); 187 | var ex = { 188 | description: examples.length > 1 ? example.description : null, 189 | request: request, 190 | url: url, 191 | dataForRequest: data, 192 | responseStatus: response_status, 193 | response: JSON.stringify(response, null, ' ') 194 | }; 195 | curlExamples.push(ex); 196 | }); 197 | return curlExamples; 198 | } 199 | 200 | /** 201 | * Modeled after Ruby's classify inflection. It turns, for example, 'custom field settings' 202 | * to 'CustomFieldSettings' 203 | */ 204 | function classify(str) { 205 | return inflect.titleize(inflect.pluralize(str)).replace(/\s/gi, ''); 206 | } 207 | 208 | 209 | /** 210 | * Construct a partial name based on a series of path parameters 211 | * The last argument (as in Ruby partials) will have a suffix appended. 212 | * (Unlike Ruby, the partial name need not start with an underscore) 213 | * Example:, the path [resource.name, "pre_description"] resolves to 214 | * "{repo_loc}/asana-api-meta/src/templates/api_reference/partials/{task, for example}/pre_description.ejs" 215 | */ 216 | function partialFilename() { 217 | var partial_path_arry = _.flatten(Array.prototype.slice.call(arguments));//Ideally: Array.from(arguments)); that's only implemented in newer JS impls 218 | var partial_path = partial_path_arry.join('/') + ".ejs"; 219 | // path.join takes variable length arguments, so we pre-calculate a standard prefix and suffix 220 | var filename = path.join(__dirname, '/templates/api_reference/partials', partial_path); 221 | return filename; 222 | } 223 | 224 | /** 225 | * Test if we can stat a partial, given the path parameters (as in partialFilename) 226 | */ 227 | function partialExists() { 228 | try { 229 | var partial_path_arry = _.flatten(Array.prototype.slice.call(arguments));//Ideally: Array.from(arguments)); that's only implemented in newer JS impls 230 | fs.lstatSync(partialFilename(partial_path_arry)); 231 | return true; 232 | } catch (e) { 233 | return false; 234 | } 235 | } 236 | 237 | /** Evaluate a partial, given the path parameters (as in partialFilename) 238 | * @param [partial_path_arry] {vararg(String, Array)}: [path, [...]] variable length path segments 239 | * @param [partial_context] {Object} : context argument for partial's evaluation environment 240 | * 241 | * Let's break that function signature down: 242 | * This function takes a variable number of strings or arrays of strings, followed by an optional 243 | * context object. The context object, if present, sets the context for the partial, i.e. sets 244 | * the variables in scope for the partial. 245 | * The path is processed as in partialFilename(), that is, is resolved to the location that contains 246 | * partials based on the arguments passed in partial_path_arry. More info on how these are processed 247 | * can be found in partialFilename(). 248 | */ 249 | function partial() { 250 | var partial_path_arry = _.flatten(Array.prototype.slice.call(arguments));//Ideally: Array.from(arguments)); that's only implemented in newer JS impls 251 | var partial_context = {}; 252 | // If the last element is not a string, we interpret it as a context for the partial. 253 | // This context is used to evaluate variables in that context. 254 | if (typeof arguments[arguments.length - 1] !== 'string') 255 | { 256 | partial_context = partial_path_arry.pop(); 257 | } 258 | // Generally, partial_path_array will be a single element like ["partialname"]. It has the flexibility to look for nested directories, though, 259 | // by specifying directories in the array - so ["directory", "subdirectory", "partialname"] elements becomes directory/subdirectory/partialname.ejs 260 | if (partialExists(partial_path_arry)) { 261 | var template_content = fs.readFileSync(partialFilename(partial_path_arry), 'utf8'); 262 | return stripWhitespace(_.template(template_content)(_.merge(partial_context, langs.api_reference))); // Mixing in langs.api_reference includes the api_reference specific functions in this file. 263 | } else { 264 | return ''; 265 | } 266 | } 267 | 268 | var common = { 269 | prefix: prefixLines, 270 | plural: inflect.pluralize, 271 | single: inflect.singularize, 272 | camel: inflect.camelize, 273 | cap: inflect.capitalize, 274 | decap: inflect.decapitalize, 275 | snake: inflect.underscore, 276 | dash: inflect.dasherize, 277 | param: inflect.parameterize, 278 | human: inflect.humanize, 279 | title: inflect.titleize, 280 | classify: classify, 281 | paramsForAction: paramsForAction, 282 | examplesForResource: examplesForResource 283 | }; 284 | 285 | var langs = { 286 | "java": _.merge({}, common, { 287 | typeName: typeNameTranslator("java"), 288 | comment: wrapStarComment 289 | }), 290 | "js": _.merge({}, common, { 291 | typeName: typeNameTranslator("js"), 292 | comment: wrapStarComment 293 | }), 294 | "php": _.merge({}, common, { 295 | typeName: typeNameTranslator("php"), 296 | comment: wrapStarComment 297 | }), 298 | "python": _.merge({}, common, { 299 | typeName: typeNameTranslator("python"), 300 | comment: wrapHashComment 301 | }), 302 | ruby: _.merge({}, common, { 303 | typeName: typeNameTranslator("ruby"), 304 | comment: wrapHashComment 305 | }), 306 | api_explorer: _.merge({}, common, { 307 | typeName: typeNameTranslator("js"), 308 | comment: wrapStarComment 309 | }), 310 | api_reference: _.merge({}, common, { 311 | typeName: typeNameTranslator("md"), 312 | indent: prefixLines, 313 | removeLineBreaks: removeLineBreaks, 314 | stripWhitespace: stripWhitespace, 315 | partialExists: partialExists, 316 | partial: partial, 317 | partialFilename: partialFilename, 318 | idIfyParamName: idIfyParamName, 319 | genericPath: genericPath, 320 | curlExamplesForAction: curlExamplesForAction, 321 | curlExamplesForKeys: curlExamplesForKeys 322 | }) 323 | }; 324 | 325 | module.exports = function(lang) { 326 | return langs[lang]; 327 | }; 328 | -------------------------------------------------------------------------------- /src/resources/custom_fields.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # See `user.yaml` for more docs on these yaml files. 3 | !include ../includes.yaml 4 | name: custom_fields 5 | comment: | 6 | 7 | Custom Fields store the metadata that is used in order to add user-specified 8 | information to tasks in Asana. Be sure to reference the [Custom 9 | Fields](/developers/documentation/getting-started/custom-fields) developer 10 | documentation for more information about how custom fields relate to various 11 | resources in Asana. 12 | 13 | Users in Asana can [lock custom 14 | fields](/guide/help/premium/custom-fields#gl-lock-fields), which will make 15 | them read-only when accessed by other users. Attempting to edit a locked 16 | custom field will return HTTP error code `403 Forbidden`. 17 | 18 | 19 | properties: 20 | # We borrow a bunch of the project templates here because they're literally 21 | # exactly the same 22 | 23 | - name: id 24 | <<: *PropType.Id 25 | comment: | 26 | Globally unique ID of the custom field. 27 | **Note: This field is under active migration to the [`gid` field](#field-gid)--please see our [blog post](/developers/documentation/getting-started/deprecations) for more information.** 28 | 29 | - name: gid 30 | <<: *PropType.Gid 31 | comment: | 32 | Globally unique ID of the custom field. 33 | 34 | - name: resource_type 35 | <<: *PropType.ResourceType 36 | comment: | 37 | The resource type of this resource. The value for this resource is always `custom_field`. 38 | example_values: 39 | - '"custom_field"' 40 | values: 41 | - name: custom_field 42 | comment: A custom field resource type. 43 | 44 | - name: resource_subtype 45 | <<: *PropType.ResourceSubtype 46 | access: Create-only 47 | comment: | 48 | The type of custom field. Must be one of the given values. 49 | example_values: 50 | - '"text"' 51 | - '"number"' 52 | - '"enum"' 53 | values: 54 | - name: text 55 | comment: A custom field of subtype `text`. Text custom fields store strings of text in Asana and have very few restrictions on content. 56 | - name: number 57 | comment: A custom field of subtype `number`. Number custom fields must contain only valid numbers and are constrained to a predefined precision. 58 | - name: enum 59 | comment: A custom field of subtype `enum`. Enum custom fields are constrained to one of a set of predefined values. 60 | 61 | - name: name 62 | <<: *PropType.CustomFieldName 63 | comment: | 64 | The name of the custom field. 65 | 66 | - name: description 67 | <<: *PropType.CustomFieldDescription 68 | comment: | 69 | [Opt In](/developers/documentation/getting-started/input-output-options). The description of the custom field. 70 | 71 | - name: type 72 | <<: *PropType.CustomFieldType 73 | access: Create-only 74 | comment: | 75 | **Deprecated: new integrations should prefer the `resource_subtype` field.** 76 | The type of the custom field. Must be one of the given values. 77 | 78 | - name: enum_options 79 | <<: *PropType.CustomFieldEnumOptions 80 | access: Read-only 81 | comment: | 82 | Only relevant for custom fields of type 'enum'. This array specifies the possible values which an `enum` custom field can adopt. To modify the enum options, refer to [working with enum options](#enum-options). 83 | 84 | - name: precision 85 | <<: *PropType.CustomFieldPrecision 86 | comment: | 87 | Only relevant for custom fields of type 'Number'. This field dictates the number of places after the decimal to round to, i.e. 0 is integer values, 1 rounds to the nearest tenth, and so on. Must be between 0 and 6, inclusive. 88 | 89 | action_classes: 90 | - name: Type-specific custom field information 91 | url: type-specific 92 | comment: | 93 | Since custom fields can be defined for one of a number of types, and these types have different data and behaviors, there are fields that are relevant to a particular type. For instance, as noted above, `enum_options` is only relevant for the `enum` type and defines the set of choices that the enum could represent. The examples below show some of these type-specific custom field definitions. 94 | example_keys: ["get-enum-custom-field-data", "get-text-custom-field-data", "get-number-custom-field-data"] 95 | - name: Get a custom field 96 | url: get-single 97 | - name: Query for custom fields 98 | url: query-metadata 99 | - name: Create a custom field 100 | url: create 101 | example_keys: ["create-enum-custom-field", "create-text-custom-field", "create-number-custom-field"] 102 | - name: Update a custom field 103 | url: update 104 | - name: Delete a custom field 105 | url: delete 106 | - name: Working with custom field enum options 107 | url: enum-options 108 | comment: | 109 | Enum options are the possible values which an enum custom field can adopt. An enum custom field must contain at least 1 enum option but no more than 50. 110 | 111 | You can add enum options to a custom field by using the `POST /custom_fields/custom-field-id/enum_options` endpoint. 112 | 113 | **It is not possible to remove or delete an enum option.** Instead, enum options can be disabled by updating the `enabled` field to `false ` with the `PUT /enum_options/enum_option-id` endpoint. Other attributes can be updated similarly. 114 | 115 | On creation of an enum option, `enabled` is always set to `true`, meaning the enum option is a selectable value for the custom field. Setting `enabled=false` is equivalent to "trashing" the enum option in the Asana web app within the "Edit Fields" dialog. The enum option will no longer be selectable but, if the enum option value was previously set within a task, the task will retain the value. 116 | 117 | Enum options are an ordered list and by default new enum options are inserted at the end. Ordering in relation to existing enum options can be specified on creation by using `insert_before` or `insert_after` to reference an existing enum option. Only one of `insert_before` and `insert_after` can be provided when creating a new enum option. 118 | 119 | An enum options list can be reordered with the `POST /custom_fields/custom-field-id/enum_options/insert` endpoint. 120 | - name: Add a new enum option 121 | url: add-option 122 | - name: Update an existing enum option 123 | url: update-option 124 | - name: Reorder enum options 125 | url: reorder-options 126 | 127 | 128 | actions: 129 | 130 | # Create, Retrieve, Update, Delete 131 | 132 | - name: create 133 | class: create 134 | method: POST 135 | path: "/custom_fields" 136 | params: 137 | - name: workspace 138 | <<: *Param.WorkspaceGid 139 | required: true 140 | comment: The workspace to create a custom field in. 141 | - name: resource_subtype 142 | <<: *Param.CustomFieldType 143 | required: true 144 | - name: type 145 | <<: *Param.CustomFieldType 146 | comment: "**Deprecated: New integrations should prefer the `resource_subtype` parameter.**" 147 | - name: name 148 | <<: *Param.CustomFieldName 149 | required: true 150 | - name: description 151 | <<: *Param.CustomFieldDescription 152 | required: false 153 | - name: precision 154 | <<: *Param.CustomFieldPrecision 155 | - name: enum_options 156 | <<: *Param.CustomFieldEnumOptions 157 | 158 | comment: | 159 | Creates a new custom field in a workspace. Every custom field is required to be created in a specific workspace, and this workspace cannot be changed once set. 160 | 161 | A custom field's `name` must be unique within a workspace and not conflict with names of existing task properties such as 'Due Date' or 'Assignee'. A custom field's `type` must be one of 'text', 'enum', or 'number'. 162 | 163 | Returns the full record of the newly created custom field. 164 | 165 | - name: findById 166 | class: get-single 167 | method: GET 168 | path: "/custom_fields/%s" 169 | params: 170 | - name: custom_field 171 | <<: *Param.CustomFieldGid 172 | required: true 173 | comment: | 174 | Returns the complete definition of a custom field's metadata. 175 | 176 | - name: findByWorkspace 177 | class: query-metadata 178 | method: GET 179 | path: "/workspaces/%s/custom_fields" 180 | params: 181 | - name: workspace 182 | <<: *Param.WorkspaceGid 183 | required: true 184 | comment: The workspace or organization to find custom field definitions in. 185 | collection: true 186 | comment: | 187 | Returns a list of the compact representation of all of the custom fields in a workspace. 188 | 189 | - name: update 190 | class: update 191 | method: PUT 192 | path: "/custom_fields/%s" 193 | params: 194 | - name: custom_field 195 | <<: *Param.CustomFieldGid 196 | required: true 197 | comment: | 198 | A specific, existing custom field can be updated by making a PUT request on the URL for that custom field. Only the fields provided in the `data` block will be updated; any unspecified fields will remain unchanged 199 | 200 | When using this method, it is best to specify only those fields you wish to change, or else you may overwrite changes made by another user since you last retrieved the custom field. 201 | 202 | An enum custom field's `enum_options` cannot be updated with this endpoint. Instead see "Work With Enum Options" for information on how to update `enum_options`. 203 | 204 | Locked custom fields can only be updated by the user who locked the field. 205 | 206 | Returns the complete updated custom field record. 207 | 208 | - name: delete 209 | class: delete 210 | method: DELETE 211 | path: "/custom_fields/%s" 212 | params: 213 | - name: custom_field 214 | <<: *Param.CustomFieldGid 215 | required: true 216 | comment: | 217 | A specific, existing custom field can be deleted by making a DELETE request on the URL for that custom field. 218 | 219 | Locked custom fields can only be deleted by the user who locked the field. 220 | 221 | Returns an empty data record. 222 | 223 | - name: createEnumOption 224 | class: add-option 225 | method: POST 226 | path: "/custom_fields/%s/enum_options" 227 | params: 228 | - name: custom_field 229 | <<: *Param.CustomFieldGid 230 | required: true 231 | - name: name 232 | <<: *Param.CustomFieldEnumOptionName 233 | required: true 234 | - name: color 235 | <<: *Param.CustomFieldEnumOptionColor 236 | - name: insert_before 237 | <<: *Param.Gid 238 | comment: An existing enum option within this custom field before which the new enum option should be inserted. Cannot be provided together with after_enum_option. 239 | - name: insert_after 240 | <<: *Param.Gid 241 | comment: An existing enum option within this custom field after which the new enum option should be inserted. Cannot be provided together with before_enum_option. 242 | 243 | comment: | 244 | Creates an enum option and adds it to this custom field's list of enum options. A custom field can have at most 50 enum options (including disabled options). By default new enum options are inserted at the end of a custom field's list. 245 | 246 | Locked custom fields can only have enum options added by the user who locked the field. 247 | 248 | Returns the full record of the newly created enum option. 249 | 250 | - name: updateEnumOption 251 | class: update-option 252 | method: PUT 253 | path: "/enum_options/%s" 254 | params: 255 | - name: enum_option 256 | <<: *Param.CustomFieldEnumOptionGid 257 | required: true 258 | - name: name 259 | <<: *Param.CustomFieldEnumOptionName 260 | required: true 261 | - name: color 262 | <<: *Param.CustomFieldEnumOptionColor 263 | - name: enabled 264 | <<: *PropType.Bool 265 | comment: Whether or not the enum option is a selectable value for the custom field. 266 | 267 | comment: | 268 | Updates an existing enum option. Enum custom fields require at least one enabled enum option. 269 | 270 | Locked custom fields can only be updated by the user who locked the field. 271 | 272 | Returns the full record of the updated enum option. 273 | 274 | - name: insertEnumOption 275 | class: reorder-options 276 | method: POST 277 | path: "/custom_fields/%s/enum_options/insert" 278 | params: 279 | - name: custom_field 280 | <<: *Param.CustomFieldGid 281 | required: true 282 | - name: enum_option 283 | <<: *Param.CustomFieldEnumOptionGid 284 | required: true 285 | comment: The ID of the enum option to relocate. 286 | - name: name 287 | <<: *Param.CustomFieldEnumOptionName 288 | required: true 289 | - name: color 290 | <<: *Param.CustomFieldEnumOptionColor 291 | - name: before_enum_option 292 | <<: *Param.Gid 293 | comment: An existing enum option within this custom field before which the new enum option should be inserted. Cannot be provided together with after_enum_option. 294 | - name: after_enum_option 295 | <<: *Param.Gid 296 | comment: An existing enum option within this custom field after which the new enum option should be inserted. Cannot be provided together with before_enum_option. 297 | 298 | comment: | 299 | Moves a particular enum option to be either before or after another specified enum option in the custom field. 300 | 301 | Locked custom fields can only be reordered by the user who locked the field. 302 | 303 | -------------------------------------------------------------------------------- /src/resources/project.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # See `user.yaml` for more docs on these yaml files. 3 | !include ../includes.yaml 4 | name: project 5 | comment: | 6 | A _project_ represents a prioritized list of tasks in Asana or a board with 7 | columns of tasks represented as cards. It exists in a single workspace or 8 | organization and is accessible to a subset of users in that workspace or 9 | organization, depending on its permissions. 10 | 11 | Projects in organizations are shared with a single team. You cannot currently 12 | change the team of a project via the API. Non-organization workspaces do not 13 | have teams and so you should not specify the team of project in a regular 14 | workspace. 15 | 16 | notes: 17 | - | 18 | Followers of a project are a subset of the members of that project. 19 | Followers of a project will receive all updates including tasks created, 20 | added and removed from that project. Members of the project have access to 21 | and will receive status updates of the project. Adding followers to a 22 | project will add them as members if they are not already, removing 23 | followers from a project will not affect membership. 24 | 25 | properties: 26 | 27 | 28 | 29 | - name: id 30 | <<: *PropType.Id 31 | comment: | 32 | Globally unique ID of the project. 33 | **Note: This field is under active migration to the [`gid` field](#field-gid)--please see our [blog post](/developers/documentation/getting-started/deprecations) for more information.** 34 | 35 | - name: gid 36 | <<: *PropType.Gid 37 | comment: | 38 | Globally unique ID of the project. 39 | 40 | - name: resource_type 41 | <<: *PropType.ResourceType 42 | comment: | 43 | The resource type of this resource. The value for this resource is always `project`. To distinguish between board and list projects see the `layout` property. 44 | example_values: 45 | - '"project"' 46 | values: 47 | - name: project 48 | comment: A project resource type. 49 | 50 | - name: name 51 | <<: *PropType.PotName 52 | comment: | 53 | Name of the project. This is generally a short sentence fragment that fits 54 | on a line in the UI for maximum readability. However, it can be longer. 55 | 56 | - name: owner 57 | <<: *PropType.User 58 | comment: | 59 | The current owner of the project, may be null. 60 | 61 | - name: current_status 62 | <<: *PropType.Status 63 | access: Read-only 64 | comment: | 65 | The most recently created status update for the project, or `null` if no update exists. See also the 66 | documentation for [project status updates](/developers/api-reference/project_statuses). 67 | 68 | - name: due_date 69 | <<: *PropType.Date 70 | comment: | 71 | **Deprecated: new integrations should prefer the due_on field.** 72 | The day on which this project is due. This takes a date with format YYYY-MM-DD. 73 | 74 | - name: due_on 75 | <<: *PropType.Date 76 | comment: | 77 | The day on which this project is due. This takes a date with format YYYY-MM-DD. 78 | 79 | - name: start_on 80 | <<: *PropType.Date 81 | comment: | 82 | The day on which this project starts. This takes a date with format YYYY-MM-DD. 83 | 84 | - name: created_at 85 | <<: *PropType.DateTime 86 | access: Read-only 87 | comment: | 88 | The time at which this project was created. 89 | 90 | - name: modified_at 91 | <<: *PropType.DateTime 92 | access: Read-only 93 | comment: | 94 | The time at which this project was last modified. 95 | notes: 96 | - | 97 | This does not currently reflect any changes in associations such as tasks 98 | or comments that may have been added or removed from the project. 99 | 100 | - name: archived 101 | <<: *PropType.Bool 102 | comment: | 103 | True if the project is archived, false if not. Archived projects do not 104 | show in the UI by default and may be treated differently for queries. 105 | 106 | - name: public 107 | <<: *PropType.Bool 108 | comment: | 109 | True if the project is public to the organization. If false, do not share this project with other users in 110 | this organization without explicitly checking to see if they have access. 111 | 112 | - name: members 113 | <<: *PropType.UserArray 114 | access: Read-only 115 | comment: | 116 | Array of users who are members of this project. 117 | 118 | - name: followers 119 | <<: *PropType.UserArray 120 | access: Read-only 121 | comment: | 122 | Array of users following this project. Followers are a subset of members who receive all notifications for a 123 | project, the default notification setting when adding members to a project in-product. 124 | 125 | - name: custom_fields 126 | <<: *PropType.CustomFieldValuesArray 127 | comment: | 128 | Array of custom field values set on the project for a custom field applied to a parent portfolio. Take care to avoid confusing these custom field values with the custom field settings in the [custom_field_settings](#field-custom_field_settings) property. Please note that the `gid` returned on each custom field value is identical to the `gid` of the custom field, which allows referencing the custom field through the [/custom_fields/{custom_field_gid}](/developers/api-reference/custom_fields#get-single) endpoint. 129 | 130 | - name: custom_field_settings 131 | <<: *PropType.CustomFieldSettingsCompactArray 132 | access: Read-only 133 | comment: | 134 | Array of custom field settings in compact form. These represent the association of custom fields with this project. Take care to avoid confusing these custom field settings with the custom field values in the [custom_fields](#field-custom_fields) property. 135 | 136 | - name: color 137 | <<: *PropType.PotColor 138 | comment: | 139 | Color of the project. Must be either `null` or one of: `dark-pink`, 140 | `dark-green`, `dark-blue`, `dark-red`, `dark-teal`, `dark-brown`, 141 | `dark-orange`, `dark-purple`, `dark-warm-gray`, `light-pink`, `light-green`, 142 | `light-blue`, `light-red`, `light-teal`, `light-yellow`, `light-orange`, 143 | `light-purple`, `light-warm-gray`. 144 | 145 | - name: notes 146 | <<: *PropType.PotNotes 147 | comment: | 148 | More detailed, free-form textual information associated with the project. 149 | 150 | - name: html_notes 151 | <<: *PropType.HtmlText 152 | comment: | 153 | [Opt In](https://asana.com/developers/documentation/getting-started/input-output-options). The notes of the project with formatting as HTML. 154 | 155 | - name: workspace 156 | <<: *PropType.Workspace 157 | comment: | 158 | The workspace or organization this project is associated with. Once created, 159 | projects cannot be moved to a different workspace. This attribute can only 160 | be specified at creation time. 161 | 162 | - name: team 163 | <<: *PropType.Team 164 | comment: | 165 | The team that this project is shared with. This field only exists for 166 | projects in organizations. 167 | 168 | - name: layout 169 | <<: *PropType.Layout 170 | comment: | 171 | The layout (board or list view) of the project. 172 | 173 | action_classes: 174 | - name: Create a project 175 | url: create 176 | - name: Get single project 177 | url: get-single 178 | - name: Update a project 179 | url: update 180 | - name: Delete a project 181 | url: delete 182 | - name: Duplicate a project 183 | url: duplicate 184 | - name: Query for projects 185 | url: query 186 | - name: Get project tasks 187 | url: get-tasks 188 | - name: Work with project sections 189 | url: sections 190 | comment: | 191 | Sections are collections of tasks within a project. The `memberships` property 192 | of a task contains the project/section pairs to which a task belongs when applicable. 193 | 194 | **Deprecation warning**: At this time, sections in a list-layout project 195 | are manipulated as if they were tasks, i.e. reordering a section involves 196 | moving the section (and all of its tasks if they are to remain in that 197 | section) to a new location in a project. (see [Task, Project, and 198 | Section Associations](/developers/api-reference/tasks#projects) for more 199 | information). This method of manipulating sections as if they are tasks will 200 | soon be deprecated in favor of the methods described in the [Sections 201 | resource](/developers/api-reference/sections). 202 | - name: Work with project memberships 203 | url: members 204 | - name: Modify custom field settings 205 | url: custom-field-settings 206 | - name: Get project's task counts 207 | url: get-task-counts 208 | 209 | actions: 210 | 211 | # Create, Retrieve, Update, Delete 212 | 213 | - name: create 214 | class: create 215 | method: POST 216 | path: "/projects" 217 | params: 218 | - name: workspace 219 | <<: *Param.WorkspaceGid 220 | required: true 221 | comment: The workspace or organization to create the project in. 222 | - name: team 223 | <<: *Param.TeamGid 224 | comment: | 225 | If creating in an organization, the specific team to create the 226 | project in. 227 | comment: | 228 | Creates a new project in a workspace or team. 229 | 230 | Every project is required to be created in a specific workspace or 231 | organization, and this cannot be changed once set. Note that you can use 232 | the `workspace` parameter regardless of whether or not it is an 233 | organization. 234 | 235 | If the workspace for your project _is_ an organization, you must also 236 | supply a `team` to share the project with. 237 | 238 | Returns the full record of the newly created project. 239 | 240 | - name: createInWorkspace 241 | class: create 242 | method: POST 243 | path: "/workspaces/%s/projects" 244 | params: 245 | - name: workspace 246 | <<: *Param.WorkspaceGid 247 | required: true 248 | comment: The workspace or organization to create the project in. 249 | comment: | 250 | If the workspace for your project _is_ an organization, you must also 251 | supply a `team` to share the project with. 252 | 253 | Returns the full record of the newly created project. 254 | 255 | - name: createInTeam 256 | class: create 257 | method: POST 258 | path: "/teams/%s/projects" 259 | params: 260 | - name: team 261 | <<: *Param.TeamGid 262 | required: true 263 | comment: The team to create the project in. 264 | comment: | 265 | Creates a project shared with the given team. 266 | 267 | Returns the full record of the newly created project. 268 | 269 | - name: findById 270 | class: get-single 271 | method: GET 272 | path: "/projects/%s" 273 | params: 274 | - name: project 275 | <<: *Param.ProjectGid 276 | required: true 277 | comment: The project to get. 278 | comment: | 279 | Returns the complete project record for a single project. 280 | 281 | - name: update 282 | class: update 283 | method: PUT 284 | path: "/projects/%s" 285 | params: 286 | - name: project 287 | <<: *Param.ProjectGid 288 | required: true 289 | comment: The project to update. 290 | comment: | 291 | A specific, existing project can be updated by making a PUT request on the 292 | URL for that project. Only the fields provided in the `data` block will be 293 | updated; any unspecified fields will remain unchanged. 294 | 295 | When using this method, it is best to specify only those fields you wish 296 | to change, or else you may overwrite changes made by another user since 297 | you last retrieved the task. 298 | 299 | Returns the complete updated project record. 300 | 301 | - name: delete 302 | class: delete 303 | method: DELETE 304 | path: "/projects/%s" 305 | params: 306 | - name: project 307 | <<: *Param.ProjectGid 308 | required: true 309 | comment: The project to delete. 310 | comment: | 311 | A specific, existing project can be deleted by making a DELETE request 312 | on the URL for that project. 313 | 314 | Returns an empty data record. 315 | 316 | - name: duplicateProject 317 | class: duplicate 318 | method: POST 319 | path: "/projects/%s/duplicate" 320 | params: 321 | - name: project 322 | <<: *Param.ProjectGid 323 | required: true 324 | comment: The project to duplicate. 325 | - name: name 326 | type: String 327 | example_values: 328 | - "Things to Buy" 329 | required: true 330 | comment: The name of the new project. 331 | - name: team 332 | <<: *Param.TeamGid 333 | required: false 334 | comment: | 335 | Sets the team of the new project. If team is not defined, the new project 336 | will be in the same team as the the original project. 337 | - name: include 338 | <<: *Param.ProjectIncludes 339 | required: false 340 | comment: | 341 | The elements that will be duplicated to the new project. 342 | Tasks are always included. 343 | - name: schedule_dates 344 | <<: *Param.DuplicateScheduleDates 345 | required: false 346 | comment: | 347 | A dictionary of options to auto-shift dates. 348 | `task_dates` must be included to use this option. 349 | Requires either `start_on` or `due_on`, but not both. 350 | `start_on` will set the first start date of the new 351 | project to the given date, while `due_on` will set the last due date 352 | to the given date. Both will offset the remaining dates by the same amount 353 | of the original project. 354 | comment: | 355 | Creates and returns a job that will asynchronously handle the duplication. 356 | 357 | - name: findAll 358 | class: query 359 | method: GET 360 | path: "/projects" 361 | collection: true 362 | comment: | 363 | Returns the compact project records for some filtered set of projects. 364 | Use one or more of the parameters provided to filter the projects returned. 365 | params: 366 | - name: workspace 367 | <<: *Param.WorkspaceGid 368 | comment: The workspace or organization to filter projects on. 369 | - name: team 370 | <<: *Param.TeamGid 371 | comment: The team to filter projects on. 372 | - name: is_template 373 | <<: *Param.Bool 374 | comment: | 375 | **Note: This parameter can only be included if a team is also defined, or the workspace is not an organization** 376 | Filters results to include only template projects. 377 | - name: archived 378 | <<: *Param.Bool 379 | comment: | 380 | Only return projects whose `archived` field takes on the value of 381 | this parameter. 382 | 383 | - name: findByWorkspace 384 | class: query 385 | method: GET 386 | path: "/workspaces/%s/projects" 387 | params: 388 | - name: workspace 389 | <<: *Param.WorkspaceGid 390 | required: true 391 | comment: The workspace or organization to find projects in. 392 | - name: is_template 393 | <<: *Param.Bool 394 | comment: | 395 | **Note: This parameter can only be included if a team is also defined, or the workspace is not an organization** 396 | Filters results to include only template projects. 397 | - name: archived 398 | <<: *Param.Bool 399 | comment: | 400 | Only return projects whose `archived` field takes on the value of 401 | this parameter. 402 | collection: true 403 | comment: | 404 | Returns the compact project records for all projects in the workspace. 405 | 406 | - name: findByTeam 407 | class: query 408 | method: GET 409 | path: "/teams/%s/projects" 410 | params: 411 | - name: team 412 | <<: *Param.TeamGid 413 | required: true 414 | comment: The team to find projects in. 415 | - name: is_template 416 | <<: *Param.Bool 417 | comment: | 418 | Filters results to include only template projects. 419 | - name: archived 420 | <<: *Param.Bool 421 | comment: | 422 | Only return projects whose `archived` field takes on the value of 423 | this parameter. 424 | collection: true 425 | comment: | 426 | Returns the compact project records for all projects in the team. 427 | 428 | - name: tasks 429 | class: get-tasks 430 | method: GET 431 | path: "/projects/%s/tasks" 432 | params: 433 | - name: project 434 | <<: *Param.ProjectGid 435 | required: true 436 | comment: The project in which to search for tasks. 437 | collection: true 438 | comment: | 439 | Returns the compact task records for all tasks within the given project, 440 | ordered by their priority within the project. Tasks can exist in more than one project at a time. 441 | 442 | - name: addFollowers 443 | class: followers 444 | method: POST 445 | path: "/projects/%s/addFollowers" 446 | params: 447 | - name: project 448 | <<: *Param.ProjectGid 449 | required: true 450 | comment: The project to add followers to. 451 | - name: followers 452 | <<: *Param.GidArray 453 | required: true 454 | comment: An array of followers to add to the project. 455 | comment: | 456 | Adds the specified list of users as followers to the project. Followers are a subset of members, therefore if 457 | the users are not already members of the project they will also become members as a result of this operation. 458 | Returns the updated project record. 459 | 460 | - name: removeFollowers 461 | class: followers 462 | method: POST 463 | path: "/projects/%s/removeFollowers" 464 | params: 465 | - name: project 466 | <<: *Param.ProjectGid 467 | required: true 468 | comment: The project to remove followers from. 469 | - name: followers 470 | <<: *Param.GidArray 471 | required: true 472 | comment: An array of followers to remove from the project. 473 | comment: | 474 | Removes the specified list of users from following the project, this will not affect project membership status. 475 | Returns the updated project record. 476 | 477 | - name: addMembers 478 | class: members 479 | method: POST 480 | path: "/projects/%s/addMembers" 481 | params: 482 | - name: project 483 | <<: *Param.ProjectGid 484 | required: true 485 | comment: The project to add members to. 486 | - name: members 487 | <<: *Param.GidArray 488 | required: true 489 | comment: An array of user ids. 490 | comment: | 491 | Adds the specified list of users as members of the project. Returns the updated project record. 492 | 493 | - name: removeMembers 494 | class: members 495 | method: POST 496 | path: "/projects/%s/removeMembers" 497 | params: 498 | - name: project 499 | <<: *Param.ProjectGid 500 | required: true 501 | comment: The project to remove members from. 502 | - name: members 503 | <<: *Param.GidArray 504 | required: true 505 | comment: An array of user ids. 506 | comment: | 507 | Removes the specified list of members from the project. Returns the updated project record. 508 | 509 | - name: addCustomFieldSetting 510 | class: custom-field-settings 511 | method: POST 512 | path: "/projects/%s/addCustomFieldSetting" 513 | comment: | 514 | Create a new custom field setting on the project. 515 | params: 516 | - name: project 517 | <<: *Param.ProjectGid 518 | required: true 519 | comment: The project to associate the custom field with 520 | - name: custom_field 521 | <<: *Param.CustomFieldGid 522 | required: true 523 | comment: The id of the custom field to associate with this project. 524 | - name: is_important 525 | <<: *Param.Bool 526 | comment: | 527 | Whether this field should be considered important to this project. 528 | - name: insert_before 529 | <<: *Param.CustomFieldSettingsGid 530 | comment: | 531 | An id of a Custom Field Settings on this project, before which the new Custom Field Settings will be added. 532 | `insert_before` and `insert_after` parameters cannot both be specified. 533 | - name: insert_after 534 | <<: *Param.CustomFieldSettingsGid 535 | comment: | 536 | An id of a Custom Field Settings on this project, after which the new Custom Field Settings will be added. 537 | `insert_before` and `insert_after` parameters cannot both be specified. 538 | 539 | - name: removeCustomFieldSetting 540 | class: custom-field-settings 541 | method: POST 542 | path: "/projects/%s/removeCustomFieldSetting" 543 | comment: | 544 | Remove a custom field setting on the project. 545 | params: 546 | - name: project 547 | <<: *Param.ProjectGid 548 | required: true 549 | comment: The project to associate the custom field with 550 | - name: custom_field 551 | <<: *Param.CustomFieldGid 552 | comment: The id of the custom field to remove from this project. 553 | 554 | - name: getTaskCounts 555 | class: get-task-counts 556 | method: GET 557 | path: "/projects/%s/task_counts" 558 | comment: | 559 | Get an object that holds task count fields. **All fields are excluded by default**. You must [opt in](https://asana.com/developers/documentation/getting-started/input-output-options) 560 | using `opt_fields` to get any information from this endpoint. 561 | 562 | This endpoint has an additional [rate limit](/developers/documentation/getting-started/rate-limits#standard) 563 | and each field counts especially high against our [cost limits](/developers/documentation/getting-started/rate-limits#cost). 564 | 565 | Milestones are just tasks, so they are included in the `num_tasks`, `num_incomplete_tasks`, and `num_completed_tasks` counts. 566 | params: 567 | - name: project 568 | <<: *Param.ProjectGid 569 | required: true 570 | comment: The project to get task counts for 571 | --------------------------------------------------------------------------------