├── .codeclimate.yml ├── .github └── workflows │ ├── build.yml │ ├── pubilsh-to-rubygem.yml │ └── publish-to-github-releases.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── .ruby-version ├── .swagger-codegen-ignore ├── .yardopts ├── Appraisals ├── CODE_OF_CONDUCT.md ├── Gemfile ├── Gemfile.lock ├── Guardfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── VERSION ├── asana.gemspec ├── bin ├── console └── setup ├── examples ├── Gemfile ├── Gemfile.lock ├── cli_app.rb ├── events.rb └── personal_access_token.rb ├── lib ├── asana.rb ├── asana │ ├── authentication.rb │ ├── authentication │ │ ├── oauth2.rb │ │ ├── oauth2 │ │ │ ├── access_token_authentication.rb │ │ │ ├── bearer_token_authentication.rb │ │ │ └── client.rb │ │ └── token_authentication.rb │ ├── client.rb │ ├── client │ │ └── configuration.rb │ ├── errors.rb │ ├── http_client.rb │ ├── http_client │ │ ├── environment_info.rb │ │ ├── error_handling.rb │ │ └── response.rb │ ├── resource_includes │ │ ├── attachment_uploading.rb │ │ ├── collection.rb │ │ ├── event.rb │ │ ├── event_subscription.rb │ │ ├── events.rb │ │ ├── registry.rb │ │ ├── resource.rb │ │ └── response_helper.rb │ ├── resources.rb │ ├── resources │ │ ├── attachment.rb │ │ ├── audit_log_api.rb │ │ ├── batch_api.rb │ │ ├── custom_field_settings.rb │ │ ├── custom_fields.rb │ │ ├── events.rb │ │ ├── gen │ │ │ ├── attachments_base.rb │ │ │ ├── audit_log_api_base.rb │ │ │ ├── batch_api_base.rb │ │ │ ├── custom_field_settings_base.rb │ │ │ ├── custom_fields_base.rb │ │ │ ├── events_base.rb │ │ │ ├── goal_relationships_base.rb │ │ │ ├── goals_base.rb │ │ │ ├── jobs_base.rb │ │ │ ├── memberships_base.rb │ │ │ ├── organization_exports_base.rb │ │ │ ├── portfolio_memberships_base.rb │ │ │ ├── portfolios_base.rb │ │ │ ├── project_briefs_base.rb │ │ │ ├── project_memberships_base.rb │ │ │ ├── project_statuses_base.rb │ │ │ ├── project_templates_base.rb │ │ │ ├── projects_base.rb │ │ │ ├── sections_base.rb │ │ │ ├── status_updates_base.rb │ │ │ ├── stories_base.rb │ │ │ ├── tags_base.rb │ │ │ ├── tasks_base.rb │ │ │ ├── team_memberships_base.rb │ │ │ ├── teams_base.rb │ │ │ ├── time_periods_base.rb │ │ │ ├── typeahead_base.rb │ │ │ ├── user_task_lists_base.rb │ │ │ ├── users_base.rb │ │ │ ├── webhooks_base.rb │ │ │ ├── workspace_memberships_base.rb │ │ │ └── workspaces_base.rb │ │ ├── goal.rb │ │ ├── goal_relationship.rb │ │ ├── job.rb │ │ ├── membership.rb │ │ ├── organization_export.rb │ │ ├── portfolio.rb │ │ ├── portfolio_membership.rb │ │ ├── project.rb │ │ ├── project_brief.rb │ │ ├── project_membership.rb │ │ ├── project_status.rb │ │ ├── project_template.rb │ │ ├── section.rb │ │ ├── status_update.rb │ │ ├── story.rb │ │ ├── tag.rb │ │ ├── task.rb │ │ ├── team.rb │ │ ├── team_membership.rb │ │ ├── time_period.rb │ │ ├── typeahead.rb │ │ ├── user.rb │ │ ├── user_task_list.rb │ │ ├── webhook.rb │ │ ├── workspace.rb │ │ └── workspace_membership.rb │ ├── ruby2_0_0_compatibility.rb │ └── version.rb └── templates │ ├── index.js │ └── resource.ejs ├── package-lock.json ├── package.json ├── samples ├── attachments_sample.yaml ├── audit_log_api_sample.yaml ├── batch_api_sample.yaml ├── custom_field_settings_sample.yaml ├── custom_fields_sample.yaml ├── events_sample.yaml ├── goal_relationships_sample.yaml ├── goals_sample.yaml ├── jobs_sample.yaml ├── memberships_sample.yaml ├── organization_exports_sample.yaml ├── portfolio_memberships_sample.yaml ├── portfolios_sample.yaml ├── project_briefs_sample.yaml ├── project_memberships_sample.yaml ├── project_statuses_sample.yaml ├── project_templates_sample.yaml ├── projects_sample.yaml ├── sections_sample.yaml ├── status_updates_sample.yaml ├── stories_sample.yaml ├── tags_sample.yaml ├── tasks_sample.yaml ├── team_memberships_sample.yaml ├── teams_sample.yaml ├── time_periods_sample.yaml ├── typeahead_sample.yaml ├── user_task_lists_sample.yaml ├── users_sample.yaml ├── webhooks_sample.yaml ├── workspace_memberships_sample.yaml └── workspaces_sample.yaml ├── spec ├── asana │ ├── authentication │ │ ├── oauth2 │ │ │ ├── access_token_authentication_spec.rb │ │ │ ├── bearer_token_authentication_spec.rb │ │ │ └── client_spec.rb │ │ └── token_authentication_spec.rb │ ├── client │ │ └── configuration_spec.rb │ ├── client_spec.rb │ ├── http_client │ │ └── error_handling_spec.rb │ ├── http_client_spec.rb │ └── resources │ │ ├── attachment_uploading_spec.rb │ │ ├── collection_spec.rb │ │ ├── events_spec.rb │ │ ├── resource_spec.rb │ │ ├── tag_spec.rb │ │ ├── task_spec.rb │ │ └── webhooks_spec.rb ├── spec_helper.rb ├── support │ ├── codegen.js │ ├── resources_helper.rb │ └── stub_api.rb └── templates │ ├── unicorn.rb │ ├── unicorn.yaml │ ├── unicorn_spec.rb │ ├── world.rb │ └── world.yaml └── swagger_templates ├── api.mustache ├── api_doc.mustache └── ruby-config.json /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | languages: 2 | Ruby: true 3 | exclude_paths: 4 | - "lib/asana/resources/*" 5 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | test: 13 | name: Run 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | ruby: [2.7, 3.0, 3.1] 18 | steps: 19 | - uses: actions/checkout@v3 20 | - uses: ruby/setup-ruby@v1 21 | with: 22 | ruby-version: ${{ matrix.ruby }} 23 | - run: bundle install 24 | - run: bundle exec rake -------------------------------------------------------------------------------- /.github/workflows/pubilsh-to-rubygem.yml: -------------------------------------------------------------------------------- 1 | name: Publish 📦 to RubyGems.org 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | jobs: 9 | publish-to-rubygems: 10 | name: Publish 📦 to RubyGems.org 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v3 15 | - name: Release 16 | uses: dawidd6/action-publish-gem@v1 17 | with: 18 | api_key: ${{secrets.RUBYGEMS_API_KEY}} -------------------------------------------------------------------------------- /.github/workflows/publish-to-github-releases.yml: -------------------------------------------------------------------------------- 1 | name: Publish to GitHub Releases 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | jobs: 9 | publish-to-github: 10 | name: Publish to GitHub Releases 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v3 15 | - name: Release 16 | uses: softprops/action-gh-release@v1 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /.bundle/ 3 | /.yardoc 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | /bin/ 11 | test.rb 12 | /node_modules/ 13 | /gemfiles/ 14 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | --require asana 4 | --order random 5 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: rubocop-rspec 2 | 3 | AllCops: 4 | TargetRubyVersion: 2.7 5 | NewCops: enable 6 | Exclude: 7 | - 'bin/**/*' 8 | - 'examples/**/*' 9 | - 'lib/asana/resources/*' 10 | - 'lib/asana/resources/gen/**/*' 11 | - 'spec/templates/unicorn.rb' 12 | - 'spec/templates/world.rb' 13 | - 'test.rb' 14 | 15 | LineLength: 16 | Max: 120 17 | 18 | Metrics/ParameterLists: 19 | Enabled: false 20 | 21 | Metrics/ClassLength: 22 | Enabled: false 23 | 24 | Metrics/MethodLength: 25 | Max: 20 26 | 27 | Metrics/CyclomaticComplexity: 28 | Max: 20 29 | 30 | Metrics/PerceivedComplexity: 31 | Max: 20 32 | 33 | RSpec/ContextWording: 34 | Enabled: false 35 | 36 | RSpec/ExampleLength: 37 | Enabled: false 38 | 39 | RSpec/MultipleExpectations: 40 | Enabled: false 41 | 42 | RSpec/NestedGroups: 43 | Enabled: false 44 | 45 | RSpec/FilePath: 46 | CustomTransform: {'OAuth2': 'oauth2'} 47 | 48 | RSpec/MultipleMemoizedHelpers: 49 | Enabled: false -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.7.6 2 | -------------------------------------------------------------------------------- /.swagger-codegen-ignore: -------------------------------------------------------------------------------- 1 | # Swagger Codegen Ignore 2 | 3 | test/* 4 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --title "Asana API Ruby Client" 2 | --readme README.md 3 | --plugin tomdoc 4 | lib 5 | - LICENSE.txt -------------------------------------------------------------------------------- /Appraisals: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | appraise 'faraday-2.7.0' do 4 | gem 'faraday', '2.7.0' 5 | end 6 | 7 | appraise 'faraday-2.6.0' do 8 | gem 'faraday', '2.6.0' 9 | end 10 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, 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, age, or religion. 6 | 7 | Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. 8 | 9 | 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. Project maintainers who do not follow the Code of Conduct may be removed from the project team. 10 | 11 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 12 | 13 | This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) 14 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | # Specify your gem's dependencies in asana.gemspec 6 | gemspec 7 | 8 | group :tools do 9 | # Currently we need to pin the version of Rubocop, due to an incompatibility 10 | # with rubocop-rspec. However, this also solves the problem that Rubocop 11 | # routinely adds new checks which can cause our build to "break" even when no 12 | # changes have been made. In this situation it's better to intentionally 13 | # upgrade Rubocop and fix issues at that time. 14 | gem 'rubocop', '~> 1.47.0' 15 | gem 'rubocop-rspec', '~> 2.18.1' 16 | 17 | gem 'oauth2', '~> 2.0.3' 18 | 19 | gem 'guard' 20 | gem 'guard-rspec' 21 | gem 'guard-rubocop' 22 | gem 'guard-yard' 23 | 24 | gem 'yard' 25 | gem 'yard-tomdoc' 26 | 27 | gem 'byebug' 28 | 29 | gem 'simplecov', require: false 30 | 31 | gem 'rack-protection', '1.5.5' 32 | end 33 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | asana (2.0.3) 5 | faraday (~> 2.0) 6 | faraday-follow_redirects 7 | faraday-multipart 8 | oauth2 (>= 1.4, < 3) 9 | 10 | GEM 11 | remote: https://rubygems.org/ 12 | specs: 13 | appraisal (2.4.1) 14 | bundler 15 | rake 16 | thor (>= 0.14.0) 17 | ast (2.4.2) 18 | byebug (11.1.3) 19 | coderay (1.1.3) 20 | diff-lcs (1.5.0) 21 | docile (1.4.0) 22 | faraday (2.7.4) 23 | faraday-net_http (>= 2.0, < 3.1) 24 | ruby2_keywords (>= 0.0.4) 25 | faraday-follow_redirects (0.3.0) 26 | faraday (>= 1, < 3) 27 | faraday-multipart (1.0.4) 28 | multipart-post (~> 2) 29 | faraday-net_http (3.0.2) 30 | ffi (1.15.5) 31 | formatador (1.1.0) 32 | guard (2.18.0) 33 | formatador (>= 0.2.4) 34 | listen (>= 2.7, < 4.0) 35 | lumberjack (>= 1.0.12, < 2.0) 36 | nenv (~> 0.1) 37 | notiffany (~> 0.0) 38 | pry (>= 0.13.0) 39 | shellany (~> 0.0) 40 | thor (>= 0.18.1) 41 | guard-compat (1.2.1) 42 | guard-rspec (4.7.3) 43 | guard (~> 2.1) 44 | guard-compat (~> 1.1) 45 | rspec (>= 2.99.0, < 4.0) 46 | guard-rubocop (1.5.0) 47 | guard (~> 2.0) 48 | rubocop (< 2.0) 49 | guard-yard (2.2.1) 50 | guard (>= 1.1.0) 51 | yard (>= 0.7.0) 52 | hashie (5.0.0) 53 | json (2.6.3) 54 | jwt (2.7.0) 55 | listen (3.8.0) 56 | rb-fsevent (~> 0.10, >= 0.10.3) 57 | rb-inotify (~> 0.9, >= 0.9.10) 58 | lumberjack (1.2.8) 59 | method_source (1.0.0) 60 | multi_xml (0.6.0) 61 | multipart-post (2.3.0) 62 | nenv (0.3.0) 63 | notiffany (0.1.3) 64 | nenv (~> 0.1) 65 | shellany (~> 0.0) 66 | oauth2 (2.0.9) 67 | faraday (>= 0.17.3, < 3.0) 68 | jwt (>= 1.0, < 3.0) 69 | multi_xml (~> 0.5) 70 | rack (>= 1.2, < 4) 71 | snaky_hash (~> 2.0) 72 | version_gem (~> 1.1) 73 | parallel (1.22.1) 74 | parser (3.2.2.0) 75 | ast (~> 2.4.1) 76 | pry (0.14.2) 77 | coderay (~> 1.1) 78 | method_source (~> 1.0) 79 | rack (3.0.7) 80 | rack-protection (1.5.5) 81 | rack 82 | rainbow (3.1.1) 83 | rake (13.0.6) 84 | rb-fsevent (0.11.2) 85 | rb-inotify (0.10.1) 86 | ffi (~> 1.0) 87 | regexp_parser (2.7.0) 88 | rexml (3.2.5) 89 | rspec (3.12.0) 90 | rspec-core (~> 3.12.0) 91 | rspec-expectations (~> 3.12.0) 92 | rspec-mocks (~> 3.12.0) 93 | rspec-core (3.12.1) 94 | rspec-support (~> 3.12.0) 95 | rspec-expectations (3.12.2) 96 | diff-lcs (>= 1.2.0, < 2.0) 97 | rspec-support (~> 3.12.0) 98 | rspec-mocks (3.12.5) 99 | diff-lcs (>= 1.2.0, < 2.0) 100 | rspec-support (~> 3.12.0) 101 | rspec-support (3.12.0) 102 | rubocop (1.47.0) 103 | json (~> 2.3) 104 | parallel (~> 1.10) 105 | parser (>= 3.2.0.0) 106 | rainbow (>= 2.2.2, < 4.0) 107 | regexp_parser (>= 1.8, < 3.0) 108 | rexml (>= 3.2.5, < 4.0) 109 | rubocop-ast (>= 1.26.0, < 2.0) 110 | ruby-progressbar (~> 1.7) 111 | unicode-display_width (>= 2.4.0, < 3.0) 112 | rubocop-ast (1.28.0) 113 | parser (>= 3.2.1.0) 114 | rubocop-capybara (2.17.1) 115 | rubocop (~> 1.41) 116 | rubocop-rspec (2.18.1) 117 | rubocop (~> 1.33) 118 | rubocop-capybara (~> 2.17) 119 | ruby-progressbar (1.13.0) 120 | ruby2_keywords (0.0.5) 121 | shellany (0.0.1) 122 | simplecov (0.22.0) 123 | docile (~> 1.1) 124 | simplecov-html (~> 0.11) 125 | simplecov_json_formatter (~> 0.1) 126 | simplecov-html (0.12.3) 127 | simplecov_json_formatter (0.1.4) 128 | snaky_hash (2.0.1) 129 | hashie 130 | version_gem (~> 1.1, >= 1.1.1) 131 | thor (1.2.1) 132 | tomparse (0.4.2) 133 | unicode-display_width (2.4.2) 134 | version_gem (1.1.2) 135 | webrick (1.7.0) 136 | yard (0.9.28) 137 | webrick (~> 1.7.0) 138 | yard-tomdoc (0.7.1) 139 | tomparse (>= 0.4.0) 140 | yard 141 | 142 | PLATFORMS 143 | ruby 144 | universal-darwin-21 145 | x86_64-linux 146 | 147 | DEPENDENCIES 148 | appraisal (~> 2.1, >= 2.1) 149 | asana! 150 | byebug 151 | guard 152 | guard-rspec 153 | guard-rubocop 154 | guard-yard 155 | oauth2 (~> 2.0.3) 156 | rack-protection (= 1.5.5) 157 | rake (~> 13.0) 158 | rspec (~> 3.2) 159 | rubocop (~> 1.47.0) 160 | rubocop-rspec (~> 2.18.1) 161 | simplecov 162 | yard 163 | yard-tomdoc 164 | 165 | BUNDLED WITH 166 | 2.3.13 167 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # A sample Guardfile 4 | # More info at https://github.com/guard/guard#readme 5 | 6 | ## Uncomment and set this to only include directories you want to watch 7 | # directories %w(app lib config test spec features) 8 | 9 | ## Uncomment to clear the screen before every task 10 | # clearing :on 11 | 12 | ## Guard internally checks for changes in the Guardfile and exits. 13 | ## If you want Guard to automatically start up again, run guard in a 14 | ## shell loop, e.g.: 15 | ## 16 | ## $ while bundle exec guard; do echo "Restarting Guard..."; done 17 | ## 18 | ## Note: if you are using the `directories` clause above and you are not 19 | ## watching the project directory ('.'), then you will want to move 20 | ## the Guardfile to a watched dir and symlink it back, e.g. 21 | # 22 | # $ mkdir config 23 | # $ mv Guardfile config/ 24 | # $ ln -s config/Guardfile . 25 | # 26 | # and, you'll have to watch "config/Guardfile" instead of "Guardfile" 27 | 28 | # NOTE: The cmd option is now required due to the increasing number of ways 29 | # rspec may be run, below are examples of the most common uses. 30 | # * bundler: 'bundle exec rspec' 31 | # * bundler binstubs: 'bin/rspec' 32 | # * spring: 'bin/rspec' (This will use spring if running and you have 33 | # installed the spring binstubs per the docs) 34 | # * zeus: 'zeus rspec' (requires the server to be started separately) 35 | # * 'just' rspec: 'rspec' 36 | 37 | guard :rspec, cmd: 'bundle exec rspec' do 38 | require 'guard/rspec/dsl' 39 | dsl = Guard::RSpec::Dsl.new(self) 40 | 41 | # Feel free to open issues for suggestions and improvements 42 | 43 | # RSpec files 44 | rspec = dsl.rspec 45 | watch(rspec.spec_helper) { rspec.spec_dir } 46 | watch(rspec.spec_support) { rspec.spec_dir } 47 | watch(rspec.spec_files) 48 | 49 | watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } 50 | 51 | # Rails files 52 | rails = dsl.rails(view_extensions: %w[erb haml slim]) 53 | dsl.watch_spec_files_for(rails.app_files) 54 | dsl.watch_spec_files_for(rails.views) 55 | 56 | watch(rails.controllers) do |m| 57 | [ 58 | rspec.spec.call("routing/#{m[1]}_routing"), 59 | rspec.spec.call("controllers/#{m[1]}_controller"), 60 | rspec.spec.call("acceptance/#{m[1]}") 61 | ] 62 | end 63 | 64 | # Rails config changes 65 | watch(rails.spec_helper) { rspec.spec_dir } 66 | watch(rails.routes) { "#{rspec.spec_dir}/routing" } 67 | watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" } 68 | 69 | # Capybara features specs 70 | watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") } 71 | 72 | # Turnip features and steps 73 | watch(%r{^spec/acceptance/(.+)\.feature$}) 74 | watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m| 75 | Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' 76 | end 77 | end 78 | 79 | guard :rubocop do 80 | watch(/.+\.rb$/) 81 | watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) } 82 | end 83 | 84 | guard 'yard' do 85 | watch(%r{app/.+\.rb}) 86 | watch(%r{lib/.+\.rb}) 87 | watch(%r{ext/.+\.c}) 88 | end 89 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Asana, Inc. 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/setup' 4 | require 'bundler/gem_tasks' 5 | require 'appraisal' 6 | require 'rspec/core/rake_task' 7 | require 'rubocop/rake_task' 8 | require 'yard' 9 | 10 | RSpec::Core::RakeTask.new(:spec) 11 | 12 | RuboCop::RakeTask.new 13 | 14 | YARD::Rake::YardocTask.new do |t| 15 | t.stats_options = ['--list-undoc'] 16 | end 17 | 18 | desc 'Generates a test resource from a YAML using the resource template.' 19 | task :codegen do 20 | # TODO: I believe this is obsolete and can be removed 21 | `node spec/support/codegen.js` 22 | end 23 | 24 | # rubocop:disable Metrics/BlockLength 25 | namespace :bump do 26 | def read_version 27 | File.readlines('./lib/asana/version.rb') 28 | .detect { |l| l =~ /VERSION/ } 29 | .scan(/VERSION = '([^']+)/).flatten.first.split('.') 30 | .map { |n| Integer(n) } 31 | end 32 | 33 | def write_version(major, minor, patch) 34 | File.write('VERSION', "#{major}.#{minor}.#{patch}") 35 | 36 | str = <<~RUBY 37 | #:nodoc: 38 | module Asana 39 | # Public: Version of the gem. 40 | VERSION = '#{major}.#{minor}.#{patch}' 41 | end 42 | RUBY 43 | File.write('./lib/asana/version.rb', str) 44 | 45 | new_version = "#{major}.#{minor}.#{patch}" 46 | system('bundle lock --update') 47 | system('git add Gemfile.lock') 48 | system('git add VERSION') 49 | system('git add lib/asana/version.rb') 50 | system(%(git commit -m "Bumped to #{new_version}" && ) + 51 | %(git tag -a v#{new_version} -m "Version #{new_version}")) 52 | puts "\nRun git push --tags origin master:master to release." 53 | end 54 | 55 | desc 'Bumps a patch version' 56 | task :patch do 57 | major, minor, patch = read_version 58 | write_version major, minor, patch + 1 59 | end 60 | 61 | desc 'Bumps a minor version' 62 | task :minor do 63 | major, minor, = read_version 64 | write_version major, minor + 1, 0 65 | end 66 | 67 | desc 'Bumps a major version' 68 | task :major do 69 | major, = read_version 70 | write_version major + 1, 0, 0 71 | end 72 | end 73 | # rubocop:enable Metrics/BlockLength 74 | 75 | desc 'Default: run the unit tests.' 76 | task default: %i[all rubocop yard] 77 | 78 | desc 'Test the plugin under all supported Rails versions.' 79 | task :all do |_t| 80 | exec('bundle exec appraisal install && bundle exec rake appraisal spec') 81 | end 82 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 2.0.3 -------------------------------------------------------------------------------- /asana.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | lib = File.expand_path('lib', __dir__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require 'asana/version' 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = 'asana' 9 | spec.version = Asana::VERSION 10 | spec.authors = ['Txus'] 11 | spec.email = ['me@txus.io'] 12 | 13 | spec.summary = 'Official Ruby client for the Asana API' 14 | spec.description = 'Official Ruby client for the Asana API' 15 | spec.homepage = 'https://github.com/asana/ruby-asana' 16 | spec.license = 'MIT' 17 | 18 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 19 | spec.bindir = 'exe' 20 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 21 | spec.require_paths = ['lib'] 22 | 23 | spec.required_ruby_version = '>= 2.7' 24 | 25 | spec.add_dependency 'faraday', '~> 2.0' 26 | spec.add_dependency 'faraday-follow_redirects' 27 | spec.add_dependency 'faraday-multipart' 28 | spec.add_dependency 'oauth2', '>= 1.4', '< 3' 29 | 30 | spec.add_development_dependency 'appraisal', '~> 2.1', '>= 2.1' 31 | spec.add_development_dependency 'rake', '~> 13.0' 32 | spec.add_development_dependency 'rspec', '~> 3.2' 33 | spec.metadata['rubygems_mfa_required'] = 'true' 34 | end 35 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "asana" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | 5 | bundle install 6 | npm install 7 | # Do any other automated setup that you need to do here 8 | -------------------------------------------------------------------------------- /examples/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'asana', path: '../' 4 | -------------------------------------------------------------------------------- /examples/Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: .. 3 | specs: 4 | asana (0.10.5) 5 | faraday (~> 1.0) 6 | faraday_middleware (~> 1.0) 7 | faraday_middleware-multi_json (~> 0.0) 8 | oauth2 (~> 1.4) 9 | 10 | GEM 11 | remote: https://rubygems.org/ 12 | specs: 13 | faraday (1.0.1) 14 | multipart-post (>= 1.2, < 3) 15 | faraday_middleware (1.0.0) 16 | faraday (~> 1.0) 17 | faraday_middleware-multi_json (0.0.6) 18 | faraday_middleware 19 | multi_json 20 | jwt (2.2.1) 21 | multi_json (1.15.0) 22 | multi_xml (0.6.0) 23 | multipart-post (2.1.1) 24 | oauth2 (1.4.4) 25 | faraday (>= 0.8, < 2.0) 26 | jwt (>= 1.0, < 3.0) 27 | multi_json (~> 1.3) 28 | multi_xml (~> 0.5) 29 | rack (>= 1.2, < 3) 30 | rack (2.2.3.1) 31 | 32 | PLATFORMS 33 | ruby 34 | 35 | DEPENDENCIES 36 | asana! 37 | 38 | BUNDLED WITH 39 | 1.17.3 40 | -------------------------------------------------------------------------------- /examples/cli_app.rb: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler.require 3 | require 'asana' 4 | 5 | id, secret = ENV['ASANA_CLIENT_ID'], ENV['ASANA_CLIENT_SECRET'] 6 | unless id && secret 7 | abort "Run this program with the env vars ASANA_CLIENT_ID and ASANA_CLIENT_SECRET.\n" \ 8 | "Refer to https://asana.com/developers/documentation/getting-started/authentication "\ 9 | "to get your credentials." \ 10 | "The redirect URI for your application should be \"urn:ietf:wg:oauth:2.0:oob\"." 11 | end 12 | 13 | access_token = Asana::Authentication::OAuth2.offline_flow(client_id: id, 14 | client_secret: secret) 15 | client = Asana::Client.new do |c| 16 | c.authentication :oauth2, access_token 17 | end 18 | 19 | puts "My Workspaces:" 20 | client.workspaces.get_workspaces.each do |workspace| 21 | puts "\t* #{workspace.name} - tags:" 22 | client.tags.get_tags_for_workspace(workspace: workspace.id).each do |tag| 23 | puts "\t\t- #{tag.name}" 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /examples/events.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require 'bundler' 3 | Bundler.require 4 | require 'asana' 5 | 6 | access_token = ENV['ASANA_ACCESS_TOKEN'] 7 | unless access_token 8 | abort "Run this program with the env var ASANA_ACCESS_TOKEN.\n" \ 9 | "Go to http://app.asana.com/-/account_api to create a personal access token." 10 | end 11 | 12 | client = Asana::Client.new do |c| 13 | c.authentication :access_token, access_token 14 | end 15 | 16 | workspace = client.workspaces.get_workspaces.first 17 | task = client.tasks.get_tasks(assignee: "me", workspace: workspace.id).first 18 | unless task 19 | task = client.tasks.create_task(workspace: workspace.id, name: "Hello world!", assignee: "me") 20 | end 21 | 22 | Thread.abort_on_exception = true 23 | 24 | Thread.new do 25 | puts "Listening for 'changed' events on #{task} in one thread..." 26 | task.events(wait: 2).lazy.select { |event| event.action == 'changed' }.each do |event| 27 | puts "#{event.user.name} changed #{event.resource}" 28 | end 29 | end 30 | 31 | Thread.new do 32 | puts "Listening for non-'changed' events on #{task} in another thread..." 33 | task.events(wait: 1).lazy.reject { |event| event.action == 'changed' }.each do |event| 34 | puts "'#{event.action}' event: #{event}" 35 | end 36 | end 37 | 38 | sleep 39 | -------------------------------------------------------------------------------- /examples/personal_access_token.rb: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler.require 3 | require 'asana' 4 | 5 | access_token = ENV['ASANA_ACCESS_TOKEN'] 6 | unless access_token 7 | abort "Run this program with the env var ASANA_ACCESS_TOKEN.\n" \ 8 | "Go to http://app.asana.com/-/account_api to create a personal access token." 9 | end 10 | 11 | client = Asana::Client.new do |c| 12 | c.authentication :access_token, access_token 13 | end 14 | 15 | puts "My Workspaces:" 16 | client.workspaces.get_workspaces.each do |workspace| 17 | puts "\t* #{workspace.name} - tags:" 18 | client.tags.get_tags_for_workspace(workspace: workspace.id).each do |tag| 19 | puts "\t\t- #{tag.name}" 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/asana.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'asana/ruby2_0_0_compatibility' 4 | require 'asana/authentication' 5 | require 'asana/resources' 6 | require 'asana/client' 7 | require 'asana/errors' 8 | require 'asana/http_client' 9 | require 'asana/version' 10 | 11 | # Public: Top-level namespace of the Asana API Ruby client. 12 | module Asana 13 | include Asana::Resources 14 | end 15 | -------------------------------------------------------------------------------- /lib/asana/authentication.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'authentication/oauth2' 4 | require_relative 'authentication/token_authentication' 5 | 6 | module Asana 7 | # Public: Authentication strategies for the Asana API. 8 | module Authentication 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/asana/authentication/oauth2.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'oauth2/bearer_token_authentication' 4 | require_relative 'oauth2/access_token_authentication' 5 | require_relative 'oauth2/client' 6 | 7 | module Asana 8 | module Authentication 9 | # Public: Deals with OAuth2 authentication. Contains a function to get an 10 | # access token throught a browserless authentication flow, needed for some 11 | # applications such as CLI applications. 12 | module OAuth2 13 | module_function 14 | 15 | # Public: Retrieves an access token through an offline authentication 16 | # flow. If your application can receive HTTP requests, you might want to 17 | # opt for a browser-based flow and use the omniauth-asana gem instead. 18 | # 19 | # Your registered application's redirect_uri should be exactly 20 | # "urn:ietf:wg:oauth:2.0:oob". 21 | # 22 | # client_id - [String] the client id of the registered Asana API 23 | # application. 24 | # client_secret - [String] the client secret of the registered Asana API 25 | # application. 26 | # 27 | # Returns an ::OAuth2::AccessToken object. 28 | # 29 | # Note: This function reads from STDIN and writes to STDOUT. It is meant 30 | # to be used only within the context of a CLI application. 31 | def offline_flow(client_id: required('client_id'), 32 | client_secret: required('client_secret')) 33 | client = Client.new(client_id: client_id, 34 | client_secret: client_secret, 35 | redirect_uri: 'urn:ietf:wg:oauth:2.0:oob') 36 | $stdout.puts '1. Go to the following URL to authorize the ' \ 37 | "application: #{client.authorize_url}" 38 | $stdout.puts '2. Paste the authorization code here: ' 39 | auth_code = $stdin.gets.chomp 40 | client.token_from_auth_code(auth_code) 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/asana/authentication/oauth2/access_token_authentication.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Asana 4 | module Authentication 5 | module OAuth2 6 | # Public: A mechanism to authenticate with an OAuth2 access token (a 7 | # bearer token and a refresh token) or just a refresh token. 8 | class AccessTokenAuthentication 9 | # Public: Builds an AccessTokenAuthentication from a refresh token and 10 | # client credentials, by refreshing into a new one. 11 | # 12 | # refresh_token - [String] a refresh token 13 | # client_id - [String] the client id of the registered Asana API 14 | # Application. 15 | # client_secret - [String] the client secret of the registered Asana API 16 | # Application. 17 | # redirect_uri - [String] the redirect uri of the registered Asana API 18 | # Application. 19 | # 20 | # Returns an [AccessTokenAuthentication] instance with a refreshed 21 | # access token. 22 | def self.from_refresh_token(refresh_token, 23 | client_id: required('client_id'), 24 | client_secret: required('client_secret'), 25 | redirect_uri: required('redirect_uri')) 26 | client = Client.new(client_id: client_id, 27 | client_secret: client_secret, 28 | redirect_uri: redirect_uri) 29 | new(client.token_from_refresh_token(refresh_token)) 30 | end 31 | 32 | # Public: Initializes a new AccessTokenAuthentication. 33 | # 34 | # access_token - [::OAuth2::AccessToken] An ::OAuth2::AccessToken 35 | # object. 36 | def initialize(access_token) 37 | @token = access_token 38 | end 39 | 40 | # Public: Configures a Faraday connection injecting a bearer token, 41 | # auto-refreshing it when needed. 42 | # 43 | # connection - [Faraday::Connection] the Faraday connection instance. 44 | # 45 | # Returns nothing. 46 | def configure(connection) 47 | @token = @token.refresh! if @token.expired? 48 | 49 | connection.request :authorization, 'Bearer', @token.token 50 | end 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/asana/authentication/oauth2/bearer_token_authentication.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Asana 4 | module Authentication 5 | module OAuth2 6 | # Public: A mechanism to authenticate with an OAuth2 bearer token obtained 7 | # somewhere, for instance through omniauth-asana. 8 | # 9 | # Note: This authentication mechanism doesn't support token refreshing. If 10 | # you'd like refreshing and you have a refresh token as well as a bearer 11 | # token, you can generate a proper access token with 12 | # {AccessTokenAuthentication.from_refresh_token}. 13 | class BearerTokenAuthentication 14 | # Public: Initializes a new BearerTokenAuthentication with a plain 15 | # bearer token. 16 | # 17 | # bearer_token - [String] a plain bearer token. 18 | def initialize(bearer_token) 19 | @token = bearer_token 20 | end 21 | 22 | # Public: Configures a Faraday connection injecting its token as an 23 | # OAuth2 bearer token. 24 | # 25 | # connection - [Faraday::Connection] the Faraday connection instance. 26 | # 27 | # Returns nothing. 28 | def configure(connection) 29 | connection.request :authorization, 'Bearer', @token 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/asana/authentication/oauth2/client.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'oauth2' 4 | 5 | module Asana 6 | module Authentication 7 | module OAuth2 8 | # Public: Deals with the details of obtaining an OAuth2 authorization URL 9 | # and obtaining access tokens from either authorization codes or refresh 10 | # tokens. 11 | class Client 12 | # Public: Initializes a new client with client credentials associated 13 | # with a registered Asana API application. 14 | # 15 | # client_id - [String] a client id from the registered application 16 | # client_secret - [String] a client secret from the registered 17 | # application 18 | # redirect_uri - [String] a redirect uri from the registered 19 | # application 20 | def initialize(client_id: required('client_id'), 21 | client_secret: required('client_secret'), 22 | redirect_uri: required('redirect_uri')) 23 | @client = ::OAuth2::Client.new(client_id, client_secret, 24 | site: 'https://app.asana.com', 25 | authorize_url: '/-/oauth_authorize', 26 | token_url: '/-/oauth_token') 27 | @redirect_uri = redirect_uri 28 | end 29 | 30 | # Public: 31 | # Returns the [String] OAuth2 authorize URL. 32 | def authorize_url 33 | @client.auth_code.authorize_url(redirect_uri: @redirect_uri) 34 | end 35 | 36 | # Public: Retrieves a token from an authorization code. 37 | # 38 | # Returns the [::OAuth2::AccessToken] token. 39 | def token_from_auth_code(auth_code) 40 | @client.auth_code.get_token(auth_code, redirect_uri: @redirect_uri) 41 | end 42 | 43 | # Public: Retrieves a token from a refresh token. 44 | # 45 | # Returns the refreshed [::OAuth2::AccessToken] token. 46 | def token_from_refresh_token(token) 47 | ::OAuth2::AccessToken.new(@client, '', refresh_token: token).refresh! 48 | end 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/asana/authentication/token_authentication.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Asana 4 | module Authentication 5 | # Public: Represents an API token authentication mechanism. 6 | class TokenAuthentication 7 | def initialize(token) 8 | @token = token 9 | end 10 | 11 | # Public: Configures a Faraday connection injecting its token as 12 | # basic auth. 13 | # 14 | # builder - [Faraday::Connection] the Faraday connection instance. 15 | # 16 | # Returns nothing. 17 | def configure(connection) 18 | connection.request :authorization, :basic, @token, '' 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/asana/errors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Asana 4 | # Public: Defines the different errors that the Asana API may throw, which the 5 | # client code may want to catch. 6 | module Errors 7 | # Public: A generic, catch-all API error. It contains the whole response 8 | # object for debugging purposes. 9 | # 10 | # Note: This exception should never be raised when there exists a more 11 | # specific subclass. 12 | APIError = Class.new(StandardError) do 13 | attr_accessor :response 14 | 15 | def to_s 16 | 'An unknown API error ocurred.' 17 | end 18 | end 19 | 20 | # Public: A 401 error. Raised when the credentials used are invalid and the 21 | # user could not be authenticated. 22 | NotAuthorized = Class.new(APIError) do 23 | def to_s 24 | 'Valid credentials were not provided with the request, so the API could ' \ 25 | 'not associate a user with the request.' 26 | end 27 | end 28 | 29 | # Public: A 402 error. Raised when the user is trying to access a feature 30 | # that requires a premium account (Payment Required). 31 | PremiumOnly = Class.new(APIError) do 32 | def to_s 33 | 'The endpoint that is being requested is only available to premium ' \ 34 | 'users.' 35 | end 36 | end 37 | 38 | # Public: A 403 error. Raised when the user doesn't have permission to 39 | # access the requested resource or to perform the requested action on it. 40 | Forbidden = Class.new(APIError) do 41 | def to_s 42 | 'The authorization and request syntax was valid but the server is refusing ' \ 43 | 'to complete the request. This can happen if you try to read or write ' \ 44 | 'to objects or properties that the user does not have access to.' 45 | end 46 | end 47 | 48 | # Public: A 404 error. Raised when the requested resource doesn't exist. 49 | NotFound = Class.new(APIError) do 50 | def to_s 51 | 'Either the request method and path supplied do not specify a known ' \ 52 | 'action in the API, or the object specified by the request does not ' \ 53 | 'exist.' 54 | end 55 | end 56 | 57 | # Public: A 500 error. Raised when there is a problem in the Asana API 58 | # server. It contains a unique phrase that can be used to identify the 59 | # problem when contacting developer support. 60 | ServerError = Class.new(APIError) do 61 | attr_accessor :phrase 62 | 63 | def initialize(phrase) 64 | super() 65 | @phrase = phrase 66 | end 67 | 68 | def to_s 69 | "There has been an error on Asana's end. Use this unique phrase to " \ 70 | 'identify the problem when contacting support: ' + %("#{@phrase}") 71 | end 72 | end 73 | 74 | # Public: A 400 error. Raised when the request was malformed or missing some 75 | # parameters. It contains a list of errors indicating the specific problems. 76 | InvalidRequest = Class.new(APIError) do 77 | attr_accessor :errors 78 | 79 | def initialize(errors) 80 | super() 81 | @errors = errors 82 | end 83 | 84 | def to_s 85 | errors.join(', ') 86 | end 87 | end 88 | 89 | # Public: A 429 error. Raised when the Asana API enforces rate-limiting on 90 | # the client to avoid overload. It contains the number of seconds to wait 91 | # before retrying the operation. 92 | RateLimitEnforced = Class.new(APIError) do 93 | attr_accessor :retry_after_seconds 94 | 95 | def initialize(retry_after_seconds) 96 | super() 97 | @retry_after_seconds = retry_after_seconds 98 | end 99 | 100 | def to_s 101 | "Retry your request after #{@retry_after_seconds} seconds." 102 | end 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /lib/asana/http_client/environment_info.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../version' 4 | require 'openssl' 5 | 6 | module Asana 7 | class HttpClient 8 | # Internal: Adds environment information to a Faraday request. 9 | class EnvironmentInfo 10 | # Internal: The default user agent to use in all requests to the API. 11 | USER_AGENT = "ruby-asana v#{Asana::VERSION}" 12 | 13 | def initialize(user_agent = nil) 14 | @user_agent = user_agent || USER_AGENT 15 | @openssl_version = OpenSSL::OPENSSL_VERSION 16 | @client_version = Asana::VERSION 17 | @os = os 18 | end 19 | 20 | # Public: Augments a Faraday connection with information about the 21 | # environment. 22 | def configure(builder) 23 | builder.headers[:user_agent] = @user_agent 24 | builder.headers[:'X-Asana-Client-Lib'] = header 25 | end 26 | 27 | private 28 | 29 | def header 30 | { os: @os, 31 | language: 'ruby', 32 | language_version: RUBY_VERSION, 33 | version: @client_version, 34 | openssl_version: @openssl_version } 35 | .map { |k, v| "#{k}=#{v}" }.join('&') 36 | end 37 | 38 | def os 39 | case RUBY_PLATFORM 40 | when /win32/, /mingw/ 41 | 'windows' 42 | when /linux/ 43 | 'linux' 44 | when /darwin/ 45 | 'darwin' 46 | when /freebsd/ 47 | 'freebsd' 48 | else 49 | 'unknown' 50 | end 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/asana/http_client/response.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Asana 4 | class HttpClient 5 | # Internal: Represents a response from the Asana API. 6 | class Response 7 | # Public: 8 | # Returns a [Faraday::Env] object for debugging. 9 | attr_reader :faraday_env 10 | # Public: 11 | # Returns the [Integer] status code of the response. 12 | attr_reader :status 13 | # Public: 14 | # Returns the [Hash] representing the parsed JSON body. 15 | attr_reader :body 16 | # Public: 17 | # Returns the [Hash] of attribute headers. 18 | attr_reader :headers 19 | 20 | # Public: Wraps a Faraday response. 21 | # 22 | # faraday_response - [Faraday::Response] the Faraday response to wrap. 23 | def initialize(faraday_response) 24 | @faraday_env = faraday_response.env 25 | @status = faraday_env.status 26 | @body = faraday_env.body 27 | @headers = faraday_response.headers 28 | end 29 | 30 | # Public: 31 | # Returns a [String] representation of the response. 32 | def to_s 33 | "#" 34 | end 35 | alias inspect to_s 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/asana/resource_includes/attachment_uploading.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'faraday/multipart' 4 | 5 | module Asana 6 | module Resources 7 | # Internal: Mixin to add the ability to upload an attachment to a specific 8 | # Asana resource (a Task, really). 9 | module AttachmentUploading 10 | # Uploads a new attachment to the resource. 11 | # 12 | # filename - [String] the absolute path of the file to upload OR the desired filename when using +io+ 13 | # mime - [String] the MIME type of the file 14 | # io - [IO] an object which returns the file's content on +#read+, e.g. a +::StringIO+ 15 | # options - [Hash] the request I/O options 16 | # data - [Hash] extra attributes to post 17 | # 18 | def attach(filename: required('filename'), 19 | mime: required('mime'), 20 | io: nil, options: {}, **data) 21 | 22 | upload = if io.nil? 23 | path = File.expand_path(filename) 24 | raise ArgumentError, "file #{filename} doesn't exist" unless File.exist?(path) 25 | 26 | Faraday::Multipart::FilePart.new(path, mime) 27 | else 28 | Faraday::Multipart::FilePart.new(io, mime, filename) 29 | end 30 | 31 | response = client.post("/#{self.class.plural_name}/#{gid}/attachments", 32 | body: data, 33 | upload: upload, 34 | options: options) 35 | 36 | Attachment.new(parse(response).first, client: client) 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/asana/resource_includes/collection.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'response_helper' 4 | 5 | module Asana 6 | module Resources 7 | # Public: Represents a paginated collection of Asana resources. 8 | class Collection 9 | include Enumerable 10 | include ResponseHelper 11 | 12 | attr_reader :elements 13 | 14 | # Public: Initializes a collection representing a page of resources of a 15 | # given type. 16 | # 17 | # (elements, extra) - [Array] an (String, Hash) tuple coming from the 18 | # response parser. 19 | # type - [Class] the type of resource that the collection 20 | # contains. Defaults to the generic Resource. 21 | # client - [Asana::Client] the client to perform requests. 22 | def initialize((elements, extra), 23 | type: Resource, 24 | client: required('client')) 25 | @elements = elements.map { |elem| type.new(elem, client: client) } 26 | @type = type 27 | @next_page_data = extra['next_page'] 28 | @client = client 29 | end 30 | 31 | # Public: Iterates over the elements of the collection. 32 | def each(&block) 33 | if block 34 | @elements.each(&block) 35 | (next_page || []).each(&block) 36 | else 37 | to_enum 38 | end 39 | end 40 | 41 | # Public: Returns the last item in the collection. 42 | def last 43 | @elements.last 44 | end 45 | 46 | # Public: Returns the size of the collection. 47 | def size 48 | to_a.size 49 | end 50 | alias length size 51 | 52 | # Public: Returns a String representation of the collection. 53 | def to_s 54 | "# [#{@elements.map(&:inspect).join(', ')}#{@next_page_data ? ', ...' : ''}]>" 55 | end 56 | alias inspect to_s 57 | 58 | # Public: Returns a new Asana::Resources::Collection with the next page 59 | # or nil if there are no more pages. Caches the result. 60 | def next_page 61 | if defined?(@next_page) 62 | @next_page 63 | else 64 | @next_page = if @next_page_data 65 | response = parse(@client.get(@next_page_data['path'])) 66 | self.class.new(response, type: @type, client: @client) 67 | end 68 | end 69 | end 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/asana/resource_includes/event.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'events' 4 | 5 | module Asana 6 | module Resources 7 | # An _event_ is an object representing a change to a resource that was 8 | # observed by an event subscription. 9 | # 10 | # In general, requesting events on a resource is faster and subject to 11 | # higher rate limits than requesting the resource itself. Additionally, 12 | # change events bubble up - listening to events on a project would include 13 | # when stories are added to tasks in the project, even on subtasks. 14 | # 15 | # Establish an initial sync token by making a request with no sync token. 16 | # The response will be a `412` error - the same as if the sync token had 17 | # expired. 18 | # 19 | # Subsequent requests should always provide the sync token from the 20 | # immediately preceding call. 21 | # 22 | # Sync tokens may not be valid if you attempt to go 'backward' in the 23 | # history by requesting previous tokens, though re-requesting the current 24 | # sync token is generally safe, and will always return the same results. 25 | # 26 | # When you receive a `412 Precondition Failed` error, it means that the sync 27 | # token is either invalid or expired. If you are attempting to keep a set of 28 | # data in sync, this signals you may need to re-crawl the data. 29 | # 30 | # Sync tokens always expire after 24 hours, but may expire sooner, depending 31 | # on load on the service. 32 | class Event < Resource 33 | attr_reader :type 34 | 35 | class << self 36 | # Returns the plural name of the resource. 37 | def plural_name 38 | 'events' 39 | end 40 | 41 | # Public: Returns an infinite collection of events on a particular 42 | # resource. 43 | # 44 | # client - [Asana::Client] the client to perform the requests. 45 | # id - [String] the id of the resource to get events from. 46 | # wait - [Integer] the number of seconds to wait between each poll. 47 | def for(client, id, wait: 1) 48 | Events.new(resource: id, client: client, wait: wait) 49 | end 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/asana/resource_includes/event_subscription.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'events' 4 | 5 | module Asana 6 | module Resources 7 | # Public: Mixin to enable a resource with the ability to fetch events about 8 | # itself. 9 | module EventSubscription 10 | # Public: Returns an infinite collection of events on the resource. 11 | def events(wait: 1, options: {}) 12 | Events.new(resource: gid, client: client, wait: wait, options: options) 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/asana/resource_includes/events.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'event' 4 | 5 | module Asana 6 | module Resources 7 | # Public: An infinite collection of events. 8 | # 9 | # Since they are infinite, if you want to filter or do other collection 10 | # operations without blocking indefinitely you should call #lazy on them to 11 | # turn them into a lazy collection. 12 | # 13 | # Examples: 14 | # 15 | # # Subscribes to an event stream and blocks indefinitely, printing 16 | # # information of every event as it comes in. 17 | # events = Events.new(resource: 'someresourceID', client: client) 18 | # events.each do |event| 19 | # puts [event.type, event.action] 20 | # end 21 | # 22 | # # Lazily filters events as they come in and prints them. 23 | # events = Events.new(resource: 'someresourceID', client: client) 24 | # events.lazy.select { |e| e.type == 'task' }.each do |event| 25 | # puts [event.type, event.action] 26 | # end 27 | # 28 | class Events 29 | include Enumerable 30 | 31 | # Public: Initializes a new Events instance, subscribed to a resource ID. 32 | # 33 | # resource - [String] a resource ID. Can be a task id or a workspace id. 34 | # client - [Asana::Client] a client to perform the requests. 35 | # wait - [Integer] the number of seconds to wait between each poll. 36 | # options - [Hash] the request I/O options 37 | def initialize(resource: required('resource'), 38 | client: required('client'), 39 | wait: 1, options: {}) 40 | @resource = resource 41 | @client = client 42 | @events = [] 43 | @wait = wait 44 | @options = options 45 | @sync = nil 46 | @last_poll = nil 47 | end 48 | 49 | # Public: Iterates indefinitely over all events happening to a particular 50 | # resource from the @sync timestamp or from now if it is nil. 51 | def each(&block) 52 | if block 53 | loop do 54 | poll if @events.empty? 55 | event = @events.shift 56 | yield event if event 57 | end 58 | else 59 | to_enum 60 | end 61 | end 62 | 63 | private 64 | 65 | # Internal: Polls and fetches all events that have occurred since the sync 66 | # token was created. Updates the sync token as it comes back from the 67 | # response. 68 | # 69 | # If we polled less than @wait seconds ago, we don't do anything. 70 | # 71 | # Notes: 72 | # 73 | # On the first request, the sync token is not passed (because it is 74 | # nil). The response will be the same as for an expired sync token, and 75 | # will include a new valid sync token. 76 | # 77 | # If the sync token is too old (which may happen from time to time) 78 | # the API will return a `412 Precondition Failed` error, and include 79 | # a fresh `sync` token in the response. 80 | def poll 81 | rate_limiting do 82 | body = @client.get('/events', 83 | params: params, 84 | options: @options).body 85 | @sync = body['sync'] 86 | @events += body.fetch('data', []).map do |event_data| 87 | Event.new(event_data, client: @client) 88 | end 89 | end 90 | end 91 | 92 | # Internal: Returns the formatted params for the poll request. 93 | def params 94 | { resource: @resource, sync: @sync }.compact 95 | end 96 | 97 | # Internal: Executes a block if at least @wait seconds have passed since 98 | # @last_poll. 99 | def rate_limiting 100 | return if @last_poll && Time.now - @last_poll <= @wait 101 | 102 | yield.tap { @last_poll = Time.now } 103 | end 104 | end 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /lib/asana/resource_includes/registry.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'resource' 4 | require 'set' 5 | 6 | module Asana 7 | module Resources 8 | # Internal: Global registry of Resource subclasses. It provides lookup from 9 | # singular and plural names to the actual class objects. 10 | # 11 | # Examples 12 | # 13 | # class Unicorn < Asana::Resources::Resource 14 | # path '/unicorns' 15 | # end 16 | # 17 | # Registry.lookup(:unicorn) # => Unicorn 18 | # Registry.lookup_many(:unicorns) # => Unicorn 19 | # 20 | module Registry 21 | class << self 22 | # Public: Registers a new resource class. 23 | # 24 | # resource_klass - [Class] the resource class. 25 | # 26 | # Returns nothing. 27 | def register(resource_klass) 28 | resources << resource_klass 29 | end 30 | 31 | # Public: Looks up a resource class by its singular name. 32 | # 33 | # singular_name - [#to_s] the name of the resource, e.g :unicorn. 34 | # 35 | # Returns the resource class or {Asana::Resources::Resource}. 36 | def lookup(singular_name) 37 | resources.detect do |klass| 38 | klass.singular_name.to_s == singular_name.to_s 39 | end || Resource 40 | end 41 | 42 | # Public: Looks up a resource class by its plural name. 43 | # 44 | # plural_name - [#to_s] the plural name of the resource, e.g :unicorns. 45 | # 46 | # Returns the resource class or {Asana::Resources::Resource}. 47 | def lookup_many(plural_name) 48 | resources.detect do |klass| 49 | klass.plural_name.to_s == plural_name.to_s 50 | end || Resource 51 | end 52 | 53 | # Internal: A set of Resource classes. 54 | # 55 | # Returns the Set, defaulting to the empty set. 56 | # 57 | # Note: this object is a mutable singleton, so it should not be accessed 58 | # from multiple threads. 59 | def resources 60 | @resources ||= Set.new 61 | end 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /lib/asana/resource_includes/resource.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'registry' 4 | require_relative 'response_helper' 5 | 6 | module Asana 7 | module Resources 8 | # Public: The base resource class which provides some sugar over common 9 | # resource functionality. 10 | class Resource 11 | include ResponseHelper 12 | extend ResponseHelper 13 | 14 | def initialize(data, client: required('client')) 15 | @_client = client 16 | @_data = data 17 | data.each do |k, v| 18 | instance_variable_set(:"@#{k}", v) if respond_to?(k) 19 | end 20 | end 21 | 22 | # If it has findById, it implements #refresh 23 | def refresh 24 | raise "#{self.class.name} does not respond to #find_by_id" unless \ 25 | self.class.respond_to?(:find_by_id) 26 | 27 | self.class.find_by_id(client, gid) 28 | end 29 | 30 | # Internal: Proxies method calls to the data, wrapping it accordingly and 31 | # caching the result by defining a real reader method. 32 | # 33 | # Returns the value for the requested property. 34 | # 35 | # Raises a NoMethodError if the property doesn't exist. 36 | def method_missing(method_name, *args) 37 | super unless respond_to_missing?(method_name, *args) 38 | cache(method_name, wrapped(to_h[method_name.to_s])) 39 | end 40 | 41 | # Internal: Guard for the method_missing proxy. Checks if the resource 42 | # actually has a specific piece of data at all. 43 | # 44 | # Returns true if the resource has the property, false otherwise. 45 | def respond_to_missing?(method_name, *) 46 | to_h.key?(method_name.to_s) 47 | end 48 | 49 | # Public: 50 | # Returns the raw Hash representation of the data. 51 | def to_h 52 | @_data 53 | end 54 | 55 | def to_s 56 | attrs = to_h.map { |k, _| "#{k}: #{public_send(k).inspect}" }.join(', ') 57 | "#" 58 | end 59 | alias inspect to_s 60 | 61 | private 62 | 63 | # Internal: The Asana::Client instance. 64 | def client 65 | @_client 66 | end 67 | 68 | # Internal: Caches a property and a value by defining a reader method for 69 | # it. 70 | # 71 | # property - [#to_s] the property 72 | # value - [Object] the corresponding value 73 | # 74 | # Returns the value. 75 | def cache(property, value) 76 | field = :"@#{property}" 77 | instance_variable_set(field, value) 78 | define_singleton_method(property) { instance_variable_get(field) } 79 | value 80 | end 81 | 82 | # Internal: Wraps a value in a more useful class if possible, namely a 83 | # Resource or a Collection. 84 | # 85 | # Returns the wrapped value or the plain value if it couldn't be wrapped. 86 | def wrapped(value) 87 | case value 88 | when Hash then Resource.new(value, client: client) 89 | when Array then value.map(&method(:wrapped)) 90 | else value 91 | end 92 | end 93 | 94 | def refresh_with(data) 95 | self.class.new(data, client: @_client) 96 | end 97 | end 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /lib/asana/resource_includes/response_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Asana 4 | module Resources 5 | # Internal: A helper to make response body parsing easier. 6 | module ResponseHelper 7 | def parse(response) 8 | data = response.body.fetch('data') do 9 | raise("Unexpected response body: #{response.body}") 10 | end 11 | extra = response.body.reject { |k, _| k == 'data' } 12 | [data, extra] 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/asana/resources.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'resource_includes/resource' 4 | require_relative 'resource_includes/collection' 5 | 6 | Dir[File.join(File.dirname(__FILE__), 'resource_includes', '*.rb')].sort.each { |resource| require resource } 7 | 8 | Dir[File.join(File.dirname(__FILE__), 'resources', '*.rb')].sort.each { |resource| require resource } 9 | 10 | module Asana 11 | # Public: Contains all the resources that the Asana API can return. 12 | module Resources 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/asana/resources/attachment.rb: -------------------------------------------------------------------------------- 1 | require_relative 'gen/attachments_base' 2 | 3 | module Asana 4 | module Resources 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 | class Attachment < AttachmentsBase 9 | 10 | 11 | attr_reader :gid 12 | 13 | attr_reader :resource_type 14 | 15 | attr_reader :created_at 16 | 17 | attr_reader :download_url 18 | 19 | attr_reader :host 20 | 21 | attr_reader :name 22 | 23 | attr_reader :parent 24 | 25 | attr_reader :view_url 26 | 27 | class << self 28 | # Returns the plural name of the resource. 29 | def plural_name 30 | 'attachments' 31 | end 32 | 33 | # Returns the full record for a single attachment. 34 | # 35 | # id - [Gid] Globally unique identifier for the attachment. 36 | # 37 | # options - [Hash] the request I/O options. 38 | def find_by_id(client, id, options: {}) 39 | 40 | self.new(parse(client.get("/attachments/#{id}", options: options)).first, client: client) 41 | end 42 | 43 | # Returns the compact records for all attachments on the task. 44 | # 45 | # task - [Gid] Globally unique identifier for the task. 46 | # 47 | # per_page - [Integer] the number of records to fetch per page. 48 | # options - [Hash] the request I/O options. 49 | def find_by_task(client, task: required("task"), per_page: 20, options: {}) 50 | params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? } 51 | Collection.new(parse(client.get("/tasks/#{task}/attachments", params: params, options: options)), type: self, client: client) 52 | end 53 | end 54 | 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/asana/resources/audit_log_api.rb: -------------------------------------------------------------------------------- 1 | require_relative 'gen/audit_log_api_base' 2 | 3 | module Asana 4 | module Resources 5 | class AuditLogAPI < AuditLogAPIBase 6 | 7 | 8 | attr_reader :gid 9 | 10 | attr_reader :actor 11 | 12 | attr_reader :context 13 | 14 | attr_reader :api_authentication_method 15 | 16 | attr_reader :client_ip_address 17 | 18 | attr_reader :context_type 19 | 20 | attr_reader :oauth_app_name 21 | 22 | attr_reader :user_agent 23 | 24 | attr_reader :created_at 25 | 26 | attr_reader :details 27 | 28 | attr_reader :event_category 29 | 30 | attr_reader :event_type 31 | 32 | attr_reader :resource 33 | 34 | class << self 35 | # Returns the plural name of the resource. 36 | def plural_name 37 | 'audit_log_apis' 38 | end 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/asana/resources/batch_api.rb: -------------------------------------------------------------------------------- 1 | require_relative 'gen/batch_api_base' 2 | 3 | module Asana 4 | module Resources 5 | class BatchAPI < BatchAPIBase 6 | 7 | class << self 8 | # Returns the plural name of the resource. 9 | def plural_name 10 | 'batch_apis' 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/asana/resources/custom_field_settings.rb: -------------------------------------------------------------------------------- 1 | require_relative 'gen/custom_field_settings_base' 2 | 3 | module Asana 4 | module Resources 5 | # Custom fields are applied to a particular project or portfolio with the 6 | # Custom Field Settings resource. This resource both represents the 7 | # many-to-many join of the Custom Field and Project or Portfolio as well as 8 | # stores information that is relevant to that particular pairing; for instance, 9 | # the `is_important` property determines some possible application-specific 10 | # handling of that custom field and parent. 11 | class CustomFieldSetting < CustomFieldSettingsBase 12 | 13 | 14 | attr_reader :gid 15 | 16 | attr_reader :resource_type 17 | 18 | attr_reader :created_at 19 | 20 | attr_reader :is_important 21 | 22 | attr_reader :parent 23 | 24 | attr_reader :project 25 | 26 | attr_reader :custom_field 27 | 28 | class << self 29 | # Returns the plural name of the resource. 30 | def plural_name 31 | 'custom_field_settings' 32 | end 33 | 34 | # Returns a list of all of the custom fields settings on a project. 35 | # 36 | # project - [Gid] The ID of the project for which to list custom field settings 37 | # per_page - [Integer] the number of records to fetch per page. 38 | # options - [Hash] the request I/O options. 39 | def find_by_project(client, project: required("project"), per_page: 20, options: {}) 40 | params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? } 41 | Collection.new(parse(client.get("/projects/#{project}/custom_field_settings", params: params, options: options)), type: Resource, client: client) 42 | end 43 | 44 | # Returns a list of all of the custom fields settings on a portfolio. 45 | # 46 | # portfolio - [Gid] The ID of the portfolio for which to list custom field settings 47 | # per_page - [Integer] the number of records to fetch per page. 48 | # options - [Hash] the request I/O options. 49 | def find_by_portfolio(client, portfolio: required("portfolio"), per_page: 20, options: {}) 50 | params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? } 51 | Collection.new(parse(client.get("/portfolios/#{portfolio}/custom_field_settings", params: params, options: options)), type: Resource, client: client) 52 | end 53 | end 54 | 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/asana/resources/events.rb: -------------------------------------------------------------------------------- 1 | require_relative 'gen/events_base' 2 | 3 | module Asana 4 | module Resources 5 | class EventResponse < EventsBase 6 | 7 | 8 | attr_reader :user 9 | 10 | attr_reader :resource 11 | 12 | attr_reader :type 13 | 14 | attr_reader :action 15 | 16 | attr_reader :parent 17 | 18 | attr_reader :created_at 19 | 20 | class << self 21 | # Returns the plural name of the resource. 22 | def plural_name 23 | 'event_responses' 24 | end 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/asana/resources/gen/audit_log_api_base.rb: -------------------------------------------------------------------------------- 1 | ### WARNING: This file is auto-generated by our OpenAPI spec. Do not 2 | ### edit it manually. 3 | 4 | require_relative '../../resource_includes/response_helper' 5 | 6 | module Asana 7 | module Resources 8 | class AuditLogAPIBase < Resource 9 | 10 | def self.inherited(base) 11 | Registry.register(base) 12 | end 13 | 14 | class << self 15 | # Get audit log events 16 | # 17 | # workspace_gid - [str] (required) Globally unique identifier for the workspace or organization. 18 | # start_at - [datetime] Filter to events created after this time (inclusive). 19 | # end_at - [datetime] Filter to events created before this time (exclusive). 20 | # event_type - [str] Filter to events of this type. Refer to the [supported audit log events](/docs/audit-log-events#supported-audit-log-events) for a full list of values. 21 | # actor_type - [str] Filter to events with an actor of this type. This only needs to be included if querying for actor types without an ID. If `actor_gid` is included, this should be excluded. 22 | # actor_gid - [str] Filter to events triggered by the actor with this ID. 23 | # resource_gid - [str] Filter to events with this resource ID. 24 | # options - [Hash] the request I/O options 25 | # > offset - [str] Offset token. An offset to the next page returned by the API. A pagination request will return an offset token, which can be used as an input parameter to the next request. If an offset is not passed in, the API will return the first page of results. 'Note: You can only pass in an offset that was returned to you via a previously paginated request.' 26 | # > limit - [int] Results per page. The number of objects to return per page. The value must be between 1 and 100. 27 | def get_audit_log_events(client, workspace_gid: required("workspace_gid"), start_at: nil, end_at: nil, event_type: nil, actor_type: nil, actor_gid: nil, resource_gid: nil, options: {}) 28 | path = "/workspaces/{workspace_gid}/audit_log_events" 29 | path["{workspace_gid}"] = workspace_gid 30 | params = { start_at: start_at, end_at: end_at, event_type: event_type, actor_type: actor_type, actor_gid: actor_gid, resource_gid: resource_gid }.reject { |_,v| v.nil? || Array(v).empty? } 31 | Collection.new(parse(client.get(path, params: params, options: options)), type: Resource, client: client) 32 | end 33 | 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/asana/resources/gen/batch_api_base.rb: -------------------------------------------------------------------------------- 1 | ### WARNING: This file is auto-generated by our OpenAPI spec. Do not 2 | ### edit it manually. 3 | 4 | require_relative '../../resource_includes/response_helper' 5 | 6 | module Asana 7 | module Resources 8 | class BatchAPIBase < Resource 9 | 10 | def self.inherited(base) 11 | Registry.register(base) 12 | end 13 | 14 | class << self 15 | # Submit parallel requests 16 | # 17 | 18 | # options - [Hash] the request I/O options 19 | # > opt_fields - [list[str]] Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options. 20 | # > opt_pretty - [bool] Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging. 21 | # data - [Hash] the attributes to POST 22 | def create_batch_request(client, options: {}, **data) 23 | path = "/batch" 24 | Collection.new(parse(client.post(path, body: data, options: options)), type: Resource, client: client) 25 | end 26 | 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/asana/resources/gen/custom_field_settings_base.rb: -------------------------------------------------------------------------------- 1 | ### WARNING: This file is auto-generated by our OpenAPI spec. Do not 2 | ### edit it manually. 3 | 4 | require_relative '../../resource_includes/response_helper' 5 | 6 | module Asana 7 | module Resources 8 | class CustomFieldSettingsBase < Resource 9 | 10 | def self.inherited(base) 11 | Registry.register(base) 12 | end 13 | 14 | class << self 15 | # Get a portfolio's custom fields 16 | # 17 | # portfolio_gid - [str] (required) Globally unique identifier for the portfolio. 18 | # options - [Hash] the request I/O options 19 | # > offset - [str] Offset token. An offset to the next page returned by the API. A pagination request will return an offset token, which can be used as an input parameter to the next request. If an offset is not passed in, the API will return the first page of results. 'Note: You can only pass in an offset that was returned to you via a previously paginated request.' 20 | # > limit - [int] Results per page. The number of objects to return per page. The value must be between 1 and 100. 21 | # > opt_fields - [list[str]] Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options. 22 | # > opt_pretty - [bool] Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging. 23 | def get_custom_field_settings_for_portfolio(client, portfolio_gid: required("portfolio_gid"), options: {}) 24 | path = "/portfolios/{portfolio_gid}/custom_field_settings" 25 | path["{portfolio_gid}"] = portfolio_gid 26 | Collection.new(parse(client.get(path, options: options)), type: CustomFieldSetting, client: client) 27 | end 28 | 29 | # Get a project's custom fields 30 | # 31 | # project_gid - [str] (required) Globally unique identifier for the project. 32 | # options - [Hash] the request I/O options 33 | # > offset - [str] Offset token. An offset to the next page returned by the API. A pagination request will return an offset token, which can be used as an input parameter to the next request. If an offset is not passed in, the API will return the first page of results. 'Note: You can only pass in an offset that was returned to you via a previously paginated request.' 34 | # > limit - [int] Results per page. The number of objects to return per page. The value must be between 1 and 100. 35 | # > opt_fields - [list[str]] Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options. 36 | # > opt_pretty - [bool] Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging. 37 | def get_custom_field_settings_for_project(client, project_gid: required("project_gid"), options: {}) 38 | path = "/projects/{project_gid}/custom_field_settings" 39 | path["{project_gid}"] = project_gid 40 | Collection.new(parse(client.get(path, options: options)), type: CustomFieldSetting, client: client) 41 | end 42 | 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/asana/resources/gen/events_base.rb: -------------------------------------------------------------------------------- 1 | ### WARNING: This file is auto-generated by our OpenAPI spec. Do not 2 | ### edit it manually. 3 | 4 | require_relative '../../resource_includes/response_helper' 5 | 6 | module Asana 7 | module Resources 8 | class EventsBase < Resource 9 | 10 | def self.inherited(base) 11 | Registry.register(base) 12 | end 13 | 14 | class << self 15 | # Get events on a resource 16 | # 17 | 18 | # resource - [str] (required) A resource ID to subscribe to. The resource can be a task or project. 19 | # sync - [str] A sync token received from the last request, or none on first sync. Events will be returned from the point in time that the sync token was generated. *Note: On your first request, omit the sync token. The response will be the same as for an expired sync token, and will include a new valid sync token.If the sync token is too old (which may happen from time to time) the API will return a `412 Precondition Failed` error, and include a fresh sync token in the response.* 20 | # options - [Hash] the request I/O options 21 | # > opt_fields - [list[str]] Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options. 22 | # > opt_pretty - [bool] Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging. 23 | def get_events(client, resource: nil, sync: nil, options: {}) 24 | path = "/events" 25 | params = { resource: resource, sync: sync }.reject { |_,v| v.nil? || Array(v).empty? } 26 | Collection.new(parse(client.get(path, params: params, options: options)), type: Resource, client: client) 27 | end 28 | 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/asana/resources/gen/jobs_base.rb: -------------------------------------------------------------------------------- 1 | ### WARNING: This file is auto-generated by our OpenAPI spec. Do not 2 | ### edit it manually. 3 | 4 | require_relative '../../resource_includes/response_helper' 5 | 6 | module Asana 7 | module Resources 8 | class JobsBase < Resource 9 | 10 | def self.inherited(base) 11 | Registry.register(base) 12 | end 13 | 14 | class << self 15 | # Get a job by id 16 | # 17 | # job_gid - [str] (required) Globally unique identifier for the job. 18 | # options - [Hash] the request I/O options 19 | # > opt_fields - [list[str]] Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options. 20 | # > opt_pretty - [bool] Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging. 21 | def get_job(client, job_gid: required("job_gid"), options: {}) 22 | path = "/jobs/{job_gid}" 23 | path["{job_gid}"] = job_gid 24 | Job.new(parse(client.get(path, options: options)).first, client: client) 25 | end 26 | 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/asana/resources/gen/organization_exports_base.rb: -------------------------------------------------------------------------------- 1 | ### WARNING: This file is auto-generated by our OpenAPI spec. Do not 2 | ### edit it manually. 3 | 4 | require_relative '../../resource_includes/response_helper' 5 | 6 | module Asana 7 | module Resources 8 | class OrganizationExportsBase < Resource 9 | 10 | def self.inherited(base) 11 | Registry.register(base) 12 | end 13 | 14 | class << self 15 | # Create an organization export request 16 | # 17 | 18 | # options - [Hash] the request I/O options 19 | # > offset - [str] Offset token. An offset to the next page returned by the API. A pagination request will return an offset token, which can be used as an input parameter to the next request. If an offset is not passed in, the API will return the first page of results. 'Note: You can only pass in an offset that was returned to you via a previously paginated request.' 20 | # > limit - [int] Results per page. The number of objects to return per page. The value must be between 1 and 100. 21 | # > opt_fields - [list[str]] Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options. 22 | # > opt_pretty - [bool] Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging. 23 | # data - [Hash] the attributes to POST 24 | def create_organization_export(client, options: {}, **data) 25 | path = "/organization_exports" 26 | OrganizationExport.new(parse(client.post(path, body: data, options: options)).first, client: client) 27 | end 28 | 29 | # Get details on an org export request 30 | # 31 | # organization_export_gid - [str] (required) Globally unique identifier for the organization export. 32 | # options - [Hash] the request I/O options 33 | # > opt_fields - [list[str]] Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options. 34 | # > opt_pretty - [bool] Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging. 35 | def get_organization_export(client, organization_export_gid: required("organization_export_gid"), options: {}) 36 | path = "/organization_exports/{organization_export_gid}" 37 | path["{organization_export_gid}"] = organization_export_gid 38 | OrganizationExport.new(parse(client.get(path, options: options)).first, client: client) 39 | end 40 | 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/asana/resources/gen/project_memberships_base.rb: -------------------------------------------------------------------------------- 1 | ### WARNING: This file is auto-generated by our OpenAPI spec. Do not 2 | ### edit it manually. 3 | 4 | require_relative '../../resource_includes/response_helper' 5 | 6 | module Asana 7 | module Resources 8 | class ProjectMembershipsBase < Resource 9 | 10 | def self.inherited(base) 11 | Registry.register(base) 12 | end 13 | 14 | class << self 15 | # Get a project membership 16 | # 17 | # project_membership_gid - [str] (required) 18 | # options - [Hash] the request I/O options 19 | # > opt_fields - [list[str]] Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options. 20 | # > opt_pretty - [bool] Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging. 21 | def get_project_membership(client, project_membership_gid: required("project_membership_gid"), options: {}) 22 | path = "/project_memberships/{project_membership_gid}" 23 | path["{project_membership_gid}"] = project_membership_gid 24 | ProjectMembership.new(parse(client.get(path, options: options)).first, client: client) 25 | end 26 | 27 | # Get memberships from a project 28 | # 29 | # project_gid - [str] (required) Globally unique identifier for the project. 30 | # user - [str] A string identifying a user. This can either be the string \"me\", an email, or the gid of a user. 31 | # options - [Hash] the request I/O options 32 | # > offset - [str] Offset token. An offset to the next page returned by the API. A pagination request will return an offset token, which can be used as an input parameter to the next request. If an offset is not passed in, the API will return the first page of results. 'Note: You can only pass in an offset that was returned to you via a previously paginated request.' 33 | # > limit - [int] Results per page. The number of objects to return per page. The value must be between 1 and 100. 34 | # > opt_fields - [list[str]] Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options. 35 | # > opt_pretty - [bool] Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging. 36 | def get_project_memberships_for_project(client, project_gid: required("project_gid"), user: nil, options: {}) 37 | path = "/projects/{project_gid}/project_memberships" 38 | path["{project_gid}"] = project_gid 39 | params = { user: user }.reject { |_,v| v.nil? || Array(v).empty? } 40 | Collection.new(parse(client.get(path, params: params, options: options)), type: ProjectMembership, client: client) 41 | end 42 | 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/asana/resources/gen/time_periods_base.rb: -------------------------------------------------------------------------------- 1 | ### WARNING: This file is auto-generated by our OpenAPI spec. Do not 2 | ### edit it manually. 3 | 4 | require_relative '../../resource_includes/response_helper' 5 | 6 | module Asana 7 | module Resources 8 | class TimePeriodsBase < Resource 9 | 10 | def self.inherited(base) 11 | Registry.register(base) 12 | end 13 | 14 | class << self 15 | # Get a time period 16 | # 17 | # time_period_gid - [str] (required) Globally unique identifier for the time period. 18 | # options - [Hash] the request I/O options 19 | # > opt_fields - [list[str]] Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options. 20 | # > opt_pretty - [bool] Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging. 21 | def get_time_period(client, time_period_gid: required("time_period_gid"), options: {}) 22 | path = "/time_periods/{time_period_gid}" 23 | path["{time_period_gid}"] = time_period_gid 24 | parse(client.get(path, options: options)).first 25 | end 26 | 27 | # Get time periods 28 | # 29 | 30 | # start_on - [date] ISO 8601 date string 31 | # end_on - [date] ISO 8601 date string 32 | # workspace - [str] (required) Globally unique identifier for the workspace. 33 | # options - [Hash] the request I/O options 34 | # > offset - [str] Offset token. An offset to the next page returned by the API. A pagination request will return an offset token, which can be used as an input parameter to the next request. If an offset is not passed in, the API will return the first page of results. 'Note: You can only pass in an offset that was returned to you via a previously paginated request.' 35 | # > limit - [int] Results per page. The number of objects to return per page. The value must be between 1 and 100. 36 | # > opt_fields - [list[str]] Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options. 37 | # > opt_pretty - [bool] Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging. 38 | def get_time_periods(client, start_on: nil, end_on: nil, workspace: nil, options: {}) 39 | path = "/time_periods" 40 | params = { start_on: start_on, end_on: end_on, workspace: workspace }.reject { |_,v| v.nil? || Array(v).empty? } 41 | Collection.new(parse(client.get(path, params: params, options: options)), type: Resource, client: client) 42 | end 43 | 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/asana/resources/gen/typeahead_base.rb: -------------------------------------------------------------------------------- 1 | ### WARNING: This file is auto-generated by our OpenAPI spec. Do not 2 | ### edit it manually. 3 | 4 | require_relative '../../resource_includes/response_helper' 5 | 6 | module Asana 7 | module Resources 8 | class TypeaheadBase < Resource 9 | 10 | def self.inherited(base) 11 | Registry.register(base) 12 | end 13 | 14 | class << self 15 | # Get objects via typeahead 16 | # 17 | # workspace_gid - [str] (required) Globally unique identifier for the workspace or organization. 18 | # resource_type - [str] (required) The type of values the typeahead should return. You can choose from one of the following: `custom_field`, `project`, `project_template`, `portfolio`, `tag`, `task`, and `user`. Note that unlike in the names of endpoints, the types listed here are in singular form (e.g. `task`). Using multiple types is not yet supported. 19 | # type - [str] *Deprecated: new integrations should prefer the resource_type field.* 20 | # query - [str] The string that will be used to search for relevant objects. If an empty string is passed in, the API will return results. 21 | # count - [int] The number of results to return. The default is 20 if this parameter is omitted, with a minimum of 1 and a maximum of 100. If there are fewer results found than requested, all will be returned. 22 | # options - [Hash] the request I/O options 23 | # > opt_fields - [list[str]] Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options. 24 | # > opt_pretty - [bool] Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging. 25 | def typeahead_for_workspace(client, workspace_gid: required("workspace_gid"), resource_type: nil, type: nil, query: nil, count: nil, options: {}) 26 | path = "/workspaces/{workspace_gid}/typeahead" 27 | path["{workspace_gid}"] = workspace_gid 28 | params = { resource_type: resource_type, type: type, query: query, count: count }.reject { |_,v| v.nil? || Array(v).empty? } 29 | Collection.new(parse(client.get(path, params: params, options: options)), type: Resource, client: client) 30 | end 31 | 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/asana/resources/gen/user_task_lists_base.rb: -------------------------------------------------------------------------------- 1 | ### WARNING: This file is auto-generated by our OpenAPI spec. Do not 2 | ### edit it manually. 3 | 4 | require_relative '../../resource_includes/response_helper' 5 | 6 | module Asana 7 | module Resources 8 | class UserTaskListsBase < Resource 9 | 10 | def self.inherited(base) 11 | Registry.register(base) 12 | end 13 | 14 | class << self 15 | # Get a user task list 16 | # 17 | # user_task_list_gid - [str] (required) Globally unique identifier for the user task list. 18 | # options - [Hash] the request I/O options 19 | # > opt_fields - [list[str]] Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options. 20 | # > opt_pretty - [bool] Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging. 21 | def get_user_task_list(client, user_task_list_gid: required("user_task_list_gid"), options: {}) 22 | path = "/user_task_lists/{user_task_list_gid}" 23 | path["{user_task_list_gid}"] = user_task_list_gid 24 | UserTaskList.new(parse(client.get(path, options: options)).first, client: client) 25 | end 26 | 27 | # Get a user's task list 28 | # 29 | # user_gid - [str] (required) A string identifying a user. This can either be the string \"me\", an email, or the gid of a user. 30 | # workspace - [str] (required) The workspace in which to get the user task list. 31 | # options - [Hash] the request I/O options 32 | # > opt_fields - [list[str]] Defines fields to return. Some requests return *compact* representations of objects in order to conserve resources and complete the request more efficiently. Other times requests return more information than you may need. This option allows you to list the exact set of fields that the API should be sure to return for the objects. The field names should be provided as paths, described below. The id of included objects will always be returned, regardless of the field options. 33 | # > opt_pretty - [bool] Provides “pretty” output. Provides the response in a “pretty” format. In the case of JSON this means doing proper line breaking and indentation to make it readable. This will take extra time and increase the response size so it is advisable only to use this during debugging. 34 | def get_user_task_list_for_user(client, user_gid: required("user_gid"), workspace: nil, options: {}) 35 | path = "/users/{user_gid}/user_task_list" 36 | path["{user_gid}"] = user_gid 37 | params = { workspace: workspace }.reject { |_,v| v.nil? || Array(v).empty? } 38 | UserTaskList.new(parse(client.get(path, params: params, options: options)).first, client: client) 39 | end 40 | 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/asana/resources/goal.rb: -------------------------------------------------------------------------------- 1 | require_relative 'gen/goals_base' 2 | 3 | module Asana 4 | module Resources 5 | class Goal < GoalsBase 6 | 7 | 8 | attr_reader :gid 9 | 10 | attr_reader :resource_type 11 | 12 | attr_reader :due_on 13 | 14 | attr_reader :html_notes 15 | 16 | attr_reader :is_workspace_level 17 | 18 | attr_reader :liked 19 | 20 | attr_reader :name 21 | 22 | attr_reader :notes 23 | 24 | attr_reader :start_on 25 | 26 | attr_reader :status 27 | 28 | attr_reader :current_status_update 29 | 30 | attr_reader :followers 31 | 32 | attr_reader :likes 33 | 34 | attr_reader :metric 35 | 36 | attr_reader :num_likes 37 | 38 | attr_reader :owner 39 | 40 | attr_reader :team 41 | 42 | attr_reader :time_period 43 | 44 | attr_reader :workspace 45 | 46 | class << self 47 | # Returns the plural name of the resource. 48 | def plural_name 49 | 'goals' 50 | end 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/asana/resources/goal_relationship.rb: -------------------------------------------------------------------------------- 1 | require_relative 'gen/goal_relationships_base' 2 | 3 | module Asana 4 | module Resources 5 | class GoalRelationship < GoalRelationshipsBase 6 | 7 | 8 | attr_reader :gid 9 | 10 | attr_reader :resource_type 11 | 12 | attr_reader :contribution_weight 13 | 14 | attr_reader :resource_subtype 15 | 16 | attr_reader :supported_goal 17 | 18 | attr_reader :owner 19 | 20 | attr_reader :supporting_resource 21 | 22 | attr_reader :supporting_resource 23 | 24 | class << self 25 | # Returns the plural name of the resource. 26 | def plural_name 27 | 'goal_relationships' 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/asana/resources/job.rb: -------------------------------------------------------------------------------- 1 | require_relative 'gen/jobs_base' 2 | 3 | module Asana 4 | module Resources 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 | class Job < JobsBase 10 | 11 | 12 | attr_reader :gid 13 | 14 | attr_reader :resource_type 15 | 16 | attr_reader :resource_subtype 17 | 18 | attr_reader :status 19 | 20 | attr_reader :new_project 21 | 22 | attr_reader :new_task 23 | 24 | class << self 25 | # Returns the plural name of the resource. 26 | def plural_name 27 | 'jobs' 28 | end 29 | 30 | # Returns the complete job record for a single job. 31 | # 32 | # id - [Gid] The job to get. 33 | # options - [Hash] the request I/O options. 34 | def find_by_id(client, id, options: {}) 35 | 36 | self.new(parse(client.get("/jobs/#{id}", options: options)).first, client: client) 37 | end 38 | end 39 | 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/asana/resources/membership.rb: -------------------------------------------------------------------------------- 1 | require_relative 'gen/memberships_base' 2 | 3 | module Asana 4 | module Resources 5 | class Membership < MembershipsBase 6 | 7 | 8 | attr_reader :gid 9 | 10 | attr_reader :resource_type 11 | 12 | class << self 13 | # Returns the plural name of the resource. 14 | def plural_name 15 | 'memberships' 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/asana/resources/organization_export.rb: -------------------------------------------------------------------------------- 1 | require_relative 'gen/organization_exports_base' 2 | 3 | module Asana 4 | module Resources 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 | class OrganizationExport < OrganizationExportsBase 19 | 20 | 21 | attr_reader :gid 22 | 23 | attr_reader :created_at 24 | 25 | attr_reader :download_url 26 | 27 | attr_reader :state 28 | 29 | attr_reader :organization 30 | 31 | class << self 32 | # Returns the plural name of the resource. 33 | def plural_name 34 | 'organization_exports' 35 | end 36 | 37 | # Returns details of a previously-requested Organization export. 38 | # 39 | # id - [Gid] Globally unique identifier for the Organization export. 40 | # 41 | # options - [Hash] the request I/O options. 42 | def find_by_id(client, id, options: {}) 43 | 44 | self.new(parse(client.get("/organization_exports/#{id}", options: options)).first, client: client) 45 | end 46 | 47 | # This method creates a request to export an Organization. Asana will complete the export at some 48 | # point after you create the request. 49 | # 50 | # organization - [Gid] Globally unique identifier for the workspace or organization. 51 | # 52 | # options - [Hash] the request I/O options. 53 | # data - [Hash] the attributes to post. 54 | def create(client, organization: required("organization"), options: {}, **data) 55 | with_params = data.merge(organization: organization).reject { |_,v| v.nil? || Array(v).empty? } 56 | Resource.new(parse(client.post("/organization_exports", body: with_params, options: options)).first, client: client) 57 | end 58 | end 59 | 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/asana/resources/portfolio_membership.rb: -------------------------------------------------------------------------------- 1 | require_relative 'gen/portfolio_memberships_base' 2 | 3 | module Asana 4 | module Resources 5 | # This object determines if a user is a member of a portfolio. 6 | class PortfolioMembership < PortfolioMembershipsBase 7 | 8 | 9 | attr_reader :gid 10 | 11 | attr_reader :resource_type 12 | 13 | attr_reader :user 14 | 15 | attr_reader :portfolio 16 | 17 | class << self 18 | # Returns the plural name of the resource. 19 | def plural_name 20 | 'portfolio_memberships' 21 | end 22 | 23 | # Returns the compact portfolio membership records for the portfolio. You must 24 | # specify `portfolio`, `portfolio` and `user`, or `workspace` and `user`. 25 | # 26 | # portfolio - [Gid] The portfolio for which to fetch memberships. 27 | # workspace - [Gid] The workspace for which to fetch memberships. 28 | # user - [String] The user to filter the memberships to. 29 | # per_page - [Integer] the number of records to fetch per page. 30 | # options - [Hash] the request I/O options. 31 | def find_all(client, portfolio: nil, workspace: nil, user: nil, per_page: 20, options: {}) 32 | params = { portfolio: portfolio, workspace: workspace, user: user, limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? } 33 | Collection.new(parse(client.get("/portfolio_memberships", params: params, options: options)), type: Resource, client: client) 34 | end 35 | 36 | # Returns the compact portfolio membership records for the portfolio. 37 | # 38 | # portfolio - [Gid] The portfolio for which to fetch memberships. 39 | # user - [String] If present, the user to filter the memberships to. 40 | # per_page - [Integer] the number of records to fetch per page. 41 | # options - [Hash] the request I/O options. 42 | def find_by_portfolio(client, portfolio: required("portfolio"), user: nil, per_page: 20, options: {}) 43 | params = { user: user, limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? } 44 | Collection.new(parse(client.get("/portfolios/#{portfolio}/portfolio_memberships", params: params, options: options)), type: Resource, client: client) 45 | end 46 | 47 | # Returns the portfolio membership record. 48 | # 49 | # id - [Gid] Globally unique identifier for the portfolio membership. 50 | # 51 | # options - [Hash] the request I/O options. 52 | def find_by_id(client, id, options: {}) 53 | 54 | self.new(parse(client.get("/portfolio_memberships/#{id}", options: options)).first, client: client) 55 | end 56 | end 57 | 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/asana/resources/project_brief.rb: -------------------------------------------------------------------------------- 1 | require_relative 'gen/project_briefs_base' 2 | 3 | module Asana 4 | module Resources 5 | class ProjectBrief < ProjectBriefsBase 6 | 7 | 8 | attr_reader :gid 9 | 10 | attr_reader :resource_type 11 | 12 | attr_reader :html_text 13 | 14 | attr_reader :title 15 | 16 | attr_reader :permalink_url 17 | 18 | attr_reader :project 19 | 20 | attr_reader :text 21 | 22 | class << self 23 | # Returns the plural name of the resource. 24 | def plural_name 25 | 'project_briefs' 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/asana/resources/project_membership.rb: -------------------------------------------------------------------------------- 1 | require_relative 'gen/project_memberships_base' 2 | 3 | module Asana 4 | module Resources 5 | # With the introduction of "comment-only" projects in Asana, a user's membership 6 | # in a project comes with associated permissions. These permissions (whether a 7 | # user has full access to the project or comment-only access) are accessible 8 | # through the project memberships endpoints described here. 9 | class ProjectMembership < ProjectMembershipsBase 10 | 11 | 12 | attr_reader :gid 13 | 14 | attr_reader :resource_type 15 | 16 | attr_reader :user 17 | 18 | attr_reader :project 19 | 20 | attr_reader :write_access 21 | 22 | class << self 23 | # Returns the plural name of the resource. 24 | def plural_name 25 | 'project_memberships' 26 | end 27 | 28 | # Returns the compact project membership records for the project. 29 | # 30 | # project - [Gid] The project for which to fetch memberships. 31 | # user - [String] If present, the user to filter the memberships to. 32 | # per_page - [Integer] the number of records to fetch per page. 33 | # options - [Hash] the request I/O options. 34 | def find_by_project(client, project: required("project"), user: nil, per_page: 20, options: {}) 35 | params = { user: user, limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? } 36 | Collection.new(parse(client.get("/projects/#{project}/project_memberships", params: params, options: options)), type: Resource, client: client) 37 | end 38 | alias_method :get_many, :find_by_project 39 | 40 | # Returns the project membership record. 41 | # 42 | # id - [Gid] Globally unique identifier for the project membership. 43 | # 44 | # options - [Hash] the request I/O options. 45 | def find_by_id(client, id, options: {}) 46 | 47 | self.new(parse(client.get("/project_memberships/#{id}", options: options)).first, client: client) 48 | end 49 | alias_method :get_single, :find_by_id 50 | end 51 | 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/asana/resources/project_status.rb: -------------------------------------------------------------------------------- 1 | require_relative 'gen/project_statuses_base' 2 | 3 | module Asana 4 | module Resources 5 | # A _project status_ is an update on the progress of a particular project, and is sent out to all project 6 | # followers when created. These updates include both text describing the update and a color code intended to 7 | # represent the overall state of the project: "green" for projects that are on track, "yellow" for projects 8 | # at risk, and "red" for projects that are behind. 9 | # 10 | # Project statuses can be created and deleted, but not modified. 11 | class ProjectStatus < ProjectStatusesBase 12 | 13 | 14 | attr_reader :gid 15 | 16 | attr_reader :resource_type 17 | 18 | attr_reader :title 19 | 20 | attr_reader :text 21 | 22 | attr_reader :html_text 23 | 24 | attr_reader :color 25 | 26 | attr_reader :created_by 27 | 28 | attr_reader :created_at 29 | 30 | class << self 31 | # Returns the plural name of the resource. 32 | def plural_name 33 | 'project_statuses' 34 | end 35 | 36 | # Creates a new status update on the project. 37 | # 38 | # Returns the full record of the newly created project status update. 39 | # 40 | # project - [Gid] The project on which to create a status update. 41 | # text - [String] The text of the project status update. 42 | # 43 | # color - [String] The color to associate with the status update. Must be one of `"red"`, `"yellow"`, or `"green"`. 44 | # 45 | # options - [Hash] the request I/O options. 46 | # data - [Hash] the attributes to post. 47 | def create_in_project(client, project: required("project"), text: required("text"), color: required("color"), options: {}, **data) 48 | with_params = data.merge(text: text, color: color).reject { |_,v| v.nil? || Array(v).empty? } 49 | Resource.new(parse(client.post("/projects/#{project}/project_statuses", body: with_params, options: options)).first, client: client) 50 | end 51 | alias_method :create, :create_in_project 52 | 53 | # Returns the compact project status update records for all updates on the project. 54 | # 55 | # project - [Gid] The project to find status updates for. 56 | # per_page - [Integer] the number of records to fetch per page. 57 | # options - [Hash] the request I/O options. 58 | def find_by_project(client, project: required("project"), per_page: 20, options: {}) 59 | params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? } 60 | Collection.new(parse(client.get("/projects/#{project}/project_statuses", params: params, options: options)), type: Resource, client: client) 61 | end 62 | 63 | # Returns the complete record for a single status update. 64 | # 65 | # id - [Gid] The project status update to get. 66 | # options - [Hash] the request I/O options. 67 | def find_by_id(client, id, options: {}) 68 | 69 | self.new(parse(client.get("/project_statuses/#{id}", options: options)).first, client: client) 70 | end 71 | end 72 | 73 | # Deletes a specific, existing project status update. 74 | # 75 | # Returns an empty data record. 76 | def delete() 77 | 78 | client.delete("/project_statuses/#{gid}") && true 79 | end 80 | 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /lib/asana/resources/project_template.rb: -------------------------------------------------------------------------------- 1 | require_relative 'gen/project_templates_base' 2 | 3 | module Asana 4 | module Resources 5 | class ProjectTemplate < ProjectTemplatesBase 6 | 7 | 8 | attr_reader :gid 9 | 10 | attr_reader :resource_type 11 | 12 | attr_reader :color 13 | 14 | attr_reader :description 15 | 16 | attr_reader :html_description 17 | 18 | attr_reader :name 19 | 20 | attr_reader :owner 21 | 22 | attr_reader :public 23 | 24 | attr_reader :requested_dates 25 | 26 | attr_reader :team 27 | 28 | class << self 29 | # Returns the plural name of the resource. 30 | def plural_name 31 | 'project_templates' 32 | end 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/asana/resources/status_update.rb: -------------------------------------------------------------------------------- 1 | require_relative 'gen/status_updates_base' 2 | 3 | module Asana 4 | module Resources 5 | class StatusUpdate < StatusUpdatesBase 6 | 7 | 8 | attr_reader :gid 9 | 10 | attr_reader :resource_type 11 | 12 | attr_reader :html_text 13 | 14 | attr_reader :resource_subtype 15 | 16 | attr_reader :status_type 17 | 18 | attr_reader :text 19 | 20 | attr_reader :title 21 | 22 | attr_reader :author 23 | 24 | attr_reader :created_at 25 | 26 | attr_reader :created_by 27 | 28 | attr_reader :hearted 29 | 30 | attr_reader :hearts 31 | 32 | attr_reader :liked 33 | 34 | attr_reader :likes 35 | 36 | attr_reader :created_at 37 | 38 | attr_reader :modified_at 39 | 40 | attr_reader :num_hearts 41 | 42 | attr_reader :num_likes 43 | 44 | attr_reader :parent 45 | 46 | class << self 47 | # Returns the plural name of the resource. 48 | def plural_name 49 | 'status_updates' 50 | end 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/asana/resources/story.rb: -------------------------------------------------------------------------------- 1 | require_relative 'gen/stories_base' 2 | 3 | module Asana 4 | module Resources 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 | class Story < StoriesBase 10 | 11 | 12 | attr_reader :gid 13 | 14 | attr_reader :resource_type 15 | 16 | attr_reader :resource_subtype 17 | 18 | attr_reader :created_at 19 | 20 | attr_reader :created_by 21 | 22 | attr_reader :liked 23 | alias_method :hearted, :liked 24 | 25 | attr_reader :likes 26 | alias_method :hearts, :likes 27 | 28 | attr_reader :num_likes 29 | 30 | attr_reader :text 31 | 32 | attr_reader :html_text 33 | 34 | attr_reader :target 35 | 36 | attr_reader :is_pinned 37 | 38 | attr_reader :is_edited 39 | 40 | attr_reader :source 41 | 42 | attr_reader :type 43 | 44 | class << self 45 | # Returns the plural name of the resource. 46 | def plural_name 47 | 'stories' 48 | end 49 | 50 | # Returns the compact records for all stories on the task. 51 | # 52 | # task - [Gid] Globally unique identifier for the task. 53 | # 54 | # per_page - [Integer] the number of records to fetch per page. 55 | # options - [Hash] the request I/O options. 56 | def find_by_task(client, task: required("task"), per_page: 20, options: {}) 57 | params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? } 58 | Collection.new(parse(client.get("/tasks/#{task}/stories", params: params, options: options)), type: self, client: client) 59 | end 60 | 61 | # Returns the full record for a single story. 62 | # 63 | # id - [Gid] Globally unique identifier for the story. 64 | # 65 | # options - [Hash] the request I/O options. 66 | def find_by_id(client, id, options: {}) 67 | 68 | self.new(parse(client.get("/stories/#{id}", options: options)).first, client: client) 69 | end 70 | 71 | # Adds a comment to a task. The comment will be authored by the 72 | # currently authenticated user, and timestamped when the server receives 73 | # the request. 74 | # 75 | # Returns the full record for the new story added to the task. 76 | # 77 | # task - [Id] Globally unique identifier for the task. 78 | # 79 | # text - [String] The plain text of the comment to add. 80 | # options - [Hash] the request I/O options. 81 | # data - [Hash] the attributes to post. 82 | def create_on_task(client, task: required("task"), text: required("text"), options: {}, **data) 83 | with_params = data.merge(text: text).reject { |_,v| v.nil? || Array(v).empty? } 84 | self.new(parse(client.post("/tasks/#{task}/stories", body: with_params, options: options)).first, client: client) 85 | end 86 | end 87 | 88 | # Updates the story and returns the full record for the updated story. 89 | # Only comment stories can have their text updated, and only comment stories and 90 | # attachment stories can be pinned. Only one of `text` and `html_text` can be specified. 91 | # 92 | # text - [String] The plain text with which to update the comment. 93 | # 94 | # html_text - [String] The rich text with which to update the comment. 95 | # is_pinned - [Boolean] Whether the story should be pinned on the resource. 96 | # options - [Hash] the request I/O options. 97 | # data - [Hash] the attributes to post. 98 | def update(text: nil, html_text: nil, is_pinned: nil, options: {}, **data) 99 | with_params = data.merge(text: text, html_text: html_text, is_pinned: is_pinned).reject { |_,v| v.nil? || Array(v).empty? } 100 | refresh_with(parse(client.put("/stories/#{gid}", body: with_params, options: options)).first) 101 | end 102 | 103 | # Deletes a story. A user can only delete stories they have created. Returns an empty data record. 104 | def delete() 105 | 106 | client.delete("/stories/#{gid}") && true 107 | end 108 | 109 | end 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /lib/asana/resources/team_membership.rb: -------------------------------------------------------------------------------- 1 | require_relative 'gen/team_memberships_base' 2 | 3 | module Asana 4 | module Resources 5 | class TeamMembership < TeamMembershipsBase 6 | 7 | 8 | attr_reader :gid 9 | 10 | attr_reader :resource_type 11 | 12 | attr_reader :user 13 | 14 | attr_reader :team 15 | 16 | attr_reader :is_guest 17 | 18 | class << self 19 | # Returns the plural name of the resource. 20 | def plural_name 21 | 'team_memberships' 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/asana/resources/time_period.rb: -------------------------------------------------------------------------------- 1 | require_relative 'gen/time_periods_base' 2 | 3 | module Asana 4 | module Resources 5 | class TimePeriod < TimePeriodsBase 6 | 7 | 8 | attr_reader :gid 9 | 10 | attr_reader :resource_type 11 | 12 | attr_reader :display_name 13 | 14 | attr_reader :end_on 15 | 16 | attr_reader :parent 17 | 18 | attr_reader :period 19 | 20 | attr_reader :start_on 21 | 22 | class << self 23 | # Returns the plural name of the resource. 24 | def plural_name 25 | 'time_periods' 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/asana/resources/typeahead.rb: -------------------------------------------------------------------------------- 1 | require_relative 'gen/typeahead_base' 2 | 3 | module Asana 4 | module Resources 5 | class Typeahead < TypeaheadBase 6 | 7 | 8 | attr_reader :gid 9 | 10 | attr_reader :resource_type 11 | 12 | attr_reader :name 13 | 14 | class << self 15 | # Returns the plural name of the resource. 16 | def plural_name 17 | 'typeahead' 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/asana/resources/user.rb: -------------------------------------------------------------------------------- 1 | require_relative 'gen/users_base' 2 | 3 | module Asana 4 | module Resources 5 | # A _user_ object represents an account in Asana that can be given access to 6 | # various workspaces, projects, and tasks. 7 | # 8 | # Like other objects in the system, users are referred to by numerical IDs. 9 | # However, the special string identifier `me` can be used anywhere 10 | # a user ID is accepted, to refer to the current authenticated user. 11 | class User < UsersBase 12 | 13 | 14 | attr_reader :gid 15 | 16 | attr_reader :resource_type 17 | 18 | attr_reader :name 19 | 20 | attr_reader :email 21 | 22 | attr_reader :photo 23 | 24 | attr_reader :workspaces 25 | 26 | class << self 27 | # Returns the plural name of the resource. 28 | def plural_name 29 | 'users' 30 | end 31 | 32 | # Returns the full user record for the currently authenticated user. 33 | # 34 | # options - [Hash] the request I/O options. 35 | def me(client, options: {}) 36 | 37 | Resource.new(parse(client.get("/users/me", options: options)).first, client: client) 38 | end 39 | 40 | # Returns the full user record for the single user with the provided ID. 41 | # 42 | # id - [String] An identifier for the user. Can be one of an email address, 43 | # the globally unique identifier for the user, or the keyword `me` 44 | # to indicate the current user making the request. 45 | # 46 | # options - [Hash] the request I/O options. 47 | def find_by_id(client, id, options: {}) 48 | 49 | self.new(parse(client.get("/users/#{id}", options: options)).first, client: client) 50 | end 51 | 52 | # Returns the user records for all users in the specified workspace or 53 | # organization. 54 | # 55 | # workspace - [Id] The workspace in which to get users. 56 | # per_page - [Integer] the number of records to fetch per page. 57 | # options - [Hash] the request I/O options. 58 | def find_by_workspace(client, workspace: required("workspace"), per_page: 20, options: {}) 59 | params = { limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? } 60 | Collection.new(parse(client.get("/workspaces/#{workspace}/users", params: params, options: options)), type: self, client: client) 61 | end 62 | 63 | # Returns the user records for all users in all workspaces and organizations 64 | # accessible to the authenticated user. Accepts an optional workspace ID 65 | # parameter. 66 | # 67 | # workspace - [Id] The workspace or organization to filter users on. 68 | # per_page - [Integer] the number of records to fetch per page. 69 | # options - [Hash] the request I/O options. 70 | def find_all(client, workspace: nil, per_page: 20, options: {}) 71 | params = { workspace: workspace, limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? } 72 | Collection.new(parse(client.get("/users", params: params, options: options)), type: self, client: client) 73 | end 74 | end 75 | 76 | # Returns all of a user's favorites in the given workspace, of the given type. 77 | # Results are given in order (The same order as Asana's sidebar). 78 | # 79 | # workspace - [Id] The workspace in which to get favorites. 80 | # resource_type - [Enum] The resource type of favorites to be returned. 81 | # options - [Hash] the request I/O options. 82 | def get_user_favorites(workspace: required("workspace"), resource_type: required("resource_type"), options: {}) 83 | params = { workspace: workspace, resource_type: resource_type }.reject { |_,v| v.nil? || Array(v).empty? } 84 | Collection.new(parse(client.get("/users/#{gid}/favorites", params: params, options: options)), type: Resource, client: client) 85 | end 86 | 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /lib/asana/resources/user_task_list.rb: -------------------------------------------------------------------------------- 1 | require_relative 'gen/user_task_lists_base' 2 | 3 | module Asana 4 | module Resources 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 | class UserTaskList < UserTaskListsBase 17 | 18 | 19 | attr_reader :gid 20 | 21 | attr_reader :resource_type 22 | 23 | attr_reader :name 24 | 25 | attr_reader :owner 26 | 27 | attr_reader :workspace 28 | 29 | class << self 30 | # Returns the plural name of the resource. 31 | def plural_name 32 | 'user_task_lists' 33 | end 34 | 35 | # Returns the full record for the user task list for the given user 36 | # 37 | # user - [String] An identifier for the user. Can be one of an email address, 38 | # the globally unique identifier for the user, or the keyword `me` 39 | # to indicate the current user making the request. 40 | # 41 | # workspace - [Gid] Globally unique identifier for the workspace or organization. 42 | # 43 | # options - [Hash] the request I/O options. 44 | def find_by_user(client, user: required("user"), workspace: required("workspace"), options: {}) 45 | params = { workspace: workspace }.reject { |_,v| v.nil? || Array(v).empty? } 46 | Resource.new(parse(client.get("/users/#{user}/user_task_list", params: params, options: options)).first, client: client) 47 | end 48 | 49 | # Returns the full record for a user task list. 50 | # 51 | # id - [Gid] Globally unique identifier for the user task list. 52 | # 53 | # options - [Hash] the request I/O options. 54 | def find_by_id(client, id, options: {}) 55 | 56 | self.new(parse(client.get("/user_task_lists/#{id}", options: options)).first, client: client) 57 | end 58 | end 59 | 60 | # Returns the compact list of tasks in a user's My Tasks list. The returned 61 | # tasks will be in order within each assignee status group of `Inbox`, 62 | # `Today`, and `Upcoming`. 63 | # 64 | # **Note:** tasks in `Later` have a different ordering in the Asana web app 65 | # than the other assignee status groups; this endpoint will still return 66 | # them in list order in `Later` (differently than they show up in Asana, 67 | # but the same order as in Asana's mobile apps). 68 | # 69 | # **Note:** Access control is enforced for this endpoint as with all Asana 70 | # API endpoints, meaning a user's private tasks will be filtered out if the 71 | # API-authenticated user does not have access to them. 72 | # 73 | # **Note:** Both complete and incomplete tasks are returned by default 74 | # unless they are filtered out (for example, setting `completed_since=now` 75 | # will return only incomplete tasks, which is the default view for "My 76 | # Tasks" in Asana.) 77 | # 78 | # completed_since - [String] Only return tasks that are either incomplete or that have been 79 | # completed since this time. 80 | # 81 | # per_page - [Integer] the number of records to fetch per page. 82 | # options - [Hash] the request I/O options. 83 | def tasks(completed_since: nil, per_page: 20, options: {}) 84 | params = { completed_since: completed_since, limit: per_page }.reject { |_,v| v.nil? || Array(v).empty? } 85 | Collection.new(parse(client.get("/user_task_lists/#{gid}/tasks", params: params, options: options)), type: Task, client: client) 86 | end 87 | 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /lib/asana/resources/workspace_membership.rb: -------------------------------------------------------------------------------- 1 | require_relative 'gen/workspace_memberships_base' 2 | 3 | module Asana 4 | module Resources 5 | class WorkspaceMembership < WorkspaceMembershipsBase 6 | 7 | 8 | attr_reader :gid 9 | 10 | attr_reader :resource_type 11 | 12 | attr_reader :user 13 | 14 | attr_reader :workspace 15 | 16 | attr_reader :user_task_list 17 | 18 | attr_reader :is_admin 19 | 20 | attr_reader :is_active 21 | 22 | attr_reader :is_guest 23 | 24 | class << self 25 | # Returns the plural name of the resource. 26 | def plural_name 27 | 'workspace_memberships' 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/asana/ruby2_0_0_compatibility.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | def required(name) 4 | raise(ArgumentError, "#{name} is a required keyword argument") 5 | end 6 | -------------------------------------------------------------------------------- /lib/asana/version.rb: -------------------------------------------------------------------------------- 1 | #:nodoc: 2 | module Asana 3 | # Public: Version of the gem. 4 | VERSION = '2.0.3' 5 | end 6 | -------------------------------------------------------------------------------- /lib/templates/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 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ruby-asana", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "devDependencies": { 8 | "inflect": "^0.3.0", 9 | "js-yaml": "^3.2.5", 10 | "lodash": "^4.17.13" 11 | } 12 | }, 13 | "node_modules/argparse": { 14 | "version": "1.0.10", 15 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 16 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 17 | "dev": true, 18 | "dependencies": { 19 | "sprintf-js": "~1.0.2" 20 | } 21 | }, 22 | "node_modules/esprima": { 23 | "version": "4.0.1", 24 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 25 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 26 | "dev": true, 27 | "bin": { 28 | "esparse": "bin/esparse.js", 29 | "esvalidate": "bin/esvalidate.js" 30 | }, 31 | "engines": { 32 | "node": ">=4" 33 | } 34 | }, 35 | "node_modules/inflect": { 36 | "version": "0.3.0", 37 | "resolved": "https://registry.npmjs.org/inflect/-/inflect-0.3.0.tgz", 38 | "integrity": "sha1-gdDqqja1CmAjC3UQBIs5xBQv5So=", 39 | "dev": true, 40 | "engines": { 41 | "node": ">=0.4" 42 | } 43 | }, 44 | "node_modules/js-yaml": { 45 | "version": "3.14.1", 46 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", 47 | "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", 48 | "dev": true, 49 | "dependencies": { 50 | "argparse": "^1.0.7", 51 | "esprima": "^4.0.0" 52 | }, 53 | "bin": { 54 | "js-yaml": "bin/js-yaml.js" 55 | } 56 | }, 57 | "node_modules/lodash": { 58 | "version": "4.17.21", 59 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 60 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 61 | "dev": true 62 | }, 63 | "node_modules/sprintf-js": { 64 | "version": "1.0.3", 65 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 66 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 67 | "dev": true 68 | } 69 | }, 70 | "dependencies": { 71 | "argparse": { 72 | "version": "1.0.10", 73 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 74 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 75 | "dev": true, 76 | "requires": { 77 | "sprintf-js": "~1.0.2" 78 | } 79 | }, 80 | "esprima": { 81 | "version": "4.0.1", 82 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 83 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 84 | "dev": true 85 | }, 86 | "inflect": { 87 | "version": "0.3.0", 88 | "resolved": "https://registry.npmjs.org/inflect/-/inflect-0.3.0.tgz", 89 | "integrity": "sha1-gdDqqja1CmAjC3UQBIs5xBQv5So=", 90 | "dev": true 91 | }, 92 | "js-yaml": { 93 | "version": "3.14.1", 94 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", 95 | "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", 96 | "dev": true, 97 | "requires": { 98 | "argparse": "^1.0.7", 99 | "esprima": "^4.0.0" 100 | } 101 | }, 102 | "lodash": { 103 | "version": "4.17.21", 104 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 105 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 106 | "dev": true 107 | }, 108 | "sprintf-js": { 109 | "version": "1.0.3", 110 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 111 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 112 | "dev": true 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "inflect": "^0.3.0", 4 | "js-yaml": "^3.2.5", 5 | "lodash": "^4.17.13" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /samples/attachments_sample.yaml: -------------------------------------------------------------------------------- 1 | attachments: 2 | create_attachment_for_object: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.attachments.create_attachment_for_object(field: "value", field: "value", options: {pretty: true}) 12 | delete_attachment: >- 13 | require 'asana' 14 | 15 | 16 | client = Asana::Client.new do |c| 17 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 18 | end 19 | 20 | 21 | result = client.attachments.delete_attachment(attachment_gid: 'attachment_gid', options: {pretty: true}) 22 | get_attachment: >- 23 | require 'asana' 24 | 25 | 26 | client = Asana::Client.new do |c| 27 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 28 | end 29 | 30 | 31 | result = client.attachments.get_attachment(attachment_gid: 'attachment_gid', param: "value", param: "value", options: {pretty: true}) 32 | get_attachments_for_object: >- 33 | require 'asana' 34 | 35 | 36 | client = Asana::Client.new do |c| 37 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 38 | end 39 | 40 | 41 | result = client.attachments.get_attachments_for_object(parent: ''parent_example'', param: "value", param: "value", options: {pretty: true}) 42 | -------------------------------------------------------------------------------- /samples/audit_log_api_sample.yaml: -------------------------------------------------------------------------------- 1 | auditlogapi: 2 | get_audit_log_events: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.audit_log_api.get_audit_log_events(workspace_gid: 'workspace_gid', param: "value", param: "value", options: {pretty: true}) 12 | -------------------------------------------------------------------------------- /samples/batch_api_sample.yaml: -------------------------------------------------------------------------------- 1 | batchapi: 2 | create_batch_request: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.batch_api.create_batch_request(field: "value", field: "value", options: {pretty: true}) 12 | -------------------------------------------------------------------------------- /samples/custom_field_settings_sample.yaml: -------------------------------------------------------------------------------- 1 | customfieldsettings: 2 | get_custom_field_settings_for_portfolio: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.custom_field_settings.get_custom_field_settings_for_portfolio(portfolio_gid: 'portfolio_gid', param: "value", param: "value", options: {pretty: true}) 12 | get_custom_field_settings_for_project: >- 13 | require 'asana' 14 | 15 | 16 | client = Asana::Client.new do |c| 17 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 18 | end 19 | 20 | 21 | result = client.custom_field_settings.get_custom_field_settings_for_project(project_gid: 'project_gid', param: "value", param: "value", options: {pretty: true}) 22 | -------------------------------------------------------------------------------- /samples/custom_fields_sample.yaml: -------------------------------------------------------------------------------- 1 | customfields: 2 | create_custom_field: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.custom_fields.create_custom_field(field: "value", field: "value", options: {pretty: true}) 12 | create_enum_option_for_custom_field: >- 13 | require 'asana' 14 | 15 | 16 | client = Asana::Client.new do |c| 17 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 18 | end 19 | 20 | 21 | result = client.custom_fields.create_enum_option_for_custom_field(custom_field_gid: 'custom_field_gid', field: "value", field: "value", options: {pretty: true}) 22 | delete_custom_field: >- 23 | require 'asana' 24 | 25 | 26 | client = Asana::Client.new do |c| 27 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 28 | end 29 | 30 | 31 | result = client.custom_fields.delete_custom_field(custom_field_gid: 'custom_field_gid', options: {pretty: true}) 32 | get_custom_field: >- 33 | require 'asana' 34 | 35 | 36 | client = Asana::Client.new do |c| 37 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 38 | end 39 | 40 | 41 | result = client.custom_fields.get_custom_field(custom_field_gid: 'custom_field_gid', param: "value", param: "value", options: {pretty: true}) 42 | get_custom_fields_for_workspace: >- 43 | require 'asana' 44 | 45 | 46 | client = Asana::Client.new do |c| 47 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 48 | end 49 | 50 | 51 | result = client.custom_fields.get_custom_fields_for_workspace(workspace_gid: 'workspace_gid', param: "value", param: "value", options: {pretty: true}) 52 | insert_enum_option_for_custom_field: >- 53 | require 'asana' 54 | 55 | 56 | client = Asana::Client.new do |c| 57 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 58 | end 59 | 60 | 61 | result = client.custom_fields.insert_enum_option_for_custom_field(custom_field_gid: 'custom_field_gid', field: "value", field: "value", options: {pretty: true}) 62 | update_custom_field: >- 63 | require 'asana' 64 | 65 | 66 | client = Asana::Client.new do |c| 67 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 68 | end 69 | 70 | 71 | result = client.custom_fields.update_custom_field(custom_field_gid: 'custom_field_gid', field: "value", field: "value", options: {pretty: true}) 72 | update_enum_option: >- 73 | require 'asana' 74 | 75 | 76 | client = Asana::Client.new do |c| 77 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 78 | end 79 | 80 | 81 | result = client.custom_fields.update_enum_option(enum_option_gid: 'enum_option_gid', field: "value", field: "value", options: {pretty: true}) 82 | -------------------------------------------------------------------------------- /samples/events_sample.yaml: -------------------------------------------------------------------------------- 1 | events: 2 | get_events: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.events.get_events(resource: ''resource_example'', param: "value", param: "value", options: {pretty: true}) 12 | -------------------------------------------------------------------------------- /samples/goal_relationships_sample.yaml: -------------------------------------------------------------------------------- 1 | goalrelationships: 2 | add_supporting_relationship: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.goal_relationships.add_supporting_relationship(goal_gid: 'goal_gid', field: "value", field: "value", options: {pretty: true}) 12 | get_goal_relationship: >- 13 | require 'asana' 14 | 15 | 16 | client = Asana::Client.new do |c| 17 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 18 | end 19 | 20 | 21 | result = client.goal_relationships.get_goal_relationship(goal_relationship_gid: 'goal_relationship_gid', param: "value", param: "value", options: {pretty: true}) 22 | get_goal_relationships: >- 23 | require 'asana' 24 | 25 | 26 | client = Asana::Client.new do |c| 27 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 28 | end 29 | 30 | 31 | result = client.goal_relationships.get_goal_relationships(supported_goal: ''supported_goal_example'', param: "value", param: "value", options: {pretty: true}) 32 | remove_supporting_relationship: >- 33 | require 'asana' 34 | 35 | 36 | client = Asana::Client.new do |c| 37 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 38 | end 39 | 40 | 41 | result = client.goal_relationships.remove_supporting_relationship(goal_gid: 'goal_gid', field: "value", field: "value", options: {pretty: true}) 42 | update_goal_relationship: >- 43 | require 'asana' 44 | 45 | 46 | client = Asana::Client.new do |c| 47 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 48 | end 49 | 50 | 51 | result = client.goal_relationships.update_goal_relationship(goal_relationship_gid: 'goal_relationship_gid', field: "value", field: "value", options: {pretty: true}) 52 | -------------------------------------------------------------------------------- /samples/goals_sample.yaml: -------------------------------------------------------------------------------- 1 | goals: 2 | add_followers: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.goals.add_followers(goal_gid: 'goal_gid', field: "value", field: "value", options: {pretty: true}) 12 | create_goal: >- 13 | require 'asana' 14 | 15 | 16 | client = Asana::Client.new do |c| 17 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 18 | end 19 | 20 | 21 | result = client.goals.create_goal(field: "value", field: "value", options: {pretty: true}) 22 | create_goal_metric: >- 23 | require 'asana' 24 | 25 | 26 | client = Asana::Client.new do |c| 27 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 28 | end 29 | 30 | 31 | result = client.goals.create_goal_metric(goal_gid: 'goal_gid', field: "value", field: "value", options: {pretty: true}) 32 | delete_goal: >- 33 | require 'asana' 34 | 35 | 36 | client = Asana::Client.new do |c| 37 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 38 | end 39 | 40 | 41 | result = client.goals.delete_goal(goal_gid: 'goal_gid', options: {pretty: true}) 42 | get_goal: >- 43 | require 'asana' 44 | 45 | 46 | client = Asana::Client.new do |c| 47 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 48 | end 49 | 50 | 51 | result = client.goals.get_goal(goal_gid: 'goal_gid', param: "value", param: "value", options: {pretty: true}) 52 | get_goals: >- 53 | require 'asana' 54 | 55 | 56 | client = Asana::Client.new do |c| 57 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 58 | end 59 | 60 | 61 | result = client.goals.get_goals(param: "value", param: "value", options: {pretty: true}) 62 | get_parent_goals_for_goal: >- 63 | require 'asana' 64 | 65 | 66 | client = Asana::Client.new do |c| 67 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 68 | end 69 | 70 | 71 | result = client.goals.get_parent_goals_for_goal(goal_gid: 'goal_gid', param: "value", param: "value", options: {pretty: true}) 72 | remove_followers: >- 73 | require 'asana' 74 | 75 | 76 | client = Asana::Client.new do |c| 77 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 78 | end 79 | 80 | 81 | result = client.goals.remove_followers(goal_gid: 'goal_gid', field: "value", field: "value", options: {pretty: true}) 82 | update_goal: >- 83 | require 'asana' 84 | 85 | 86 | client = Asana::Client.new do |c| 87 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 88 | end 89 | 90 | 91 | result = client.goals.update_goal(goal_gid: 'goal_gid', field: "value", field: "value", options: {pretty: true}) 92 | update_goal_metric: >- 93 | require 'asana' 94 | 95 | 96 | client = Asana::Client.new do |c| 97 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 98 | end 99 | 100 | 101 | result = client.goals.update_goal_metric(goal_gid: 'goal_gid', field: "value", field: "value", options: {pretty: true}) 102 | -------------------------------------------------------------------------------- /samples/jobs_sample.yaml: -------------------------------------------------------------------------------- 1 | jobs: 2 | get_job: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.jobs.get_job(job_gid: 'job_gid', param: "value", param: "value", options: {pretty: true}) 12 | -------------------------------------------------------------------------------- /samples/memberships_sample.yaml: -------------------------------------------------------------------------------- 1 | memberships: 2 | create_membership: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.memberships.create_membership(field: "value", field: "value", options: {pretty: true}) 12 | delete_membership: >- 13 | require 'asana' 14 | 15 | 16 | client = Asana::Client.new do |c| 17 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 18 | end 19 | 20 | 21 | result = client.memberships.delete_membership(membership_gid: 'membership_gid', options: {pretty: true}) 22 | get_memberships: >- 23 | require 'asana' 24 | 25 | 26 | client = Asana::Client.new do |c| 27 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 28 | end 29 | 30 | 31 | result = client.memberships.get_memberships(parent: ''parent_example'', param: "value", param: "value", options: {pretty: true}) 32 | update_membership: >- 33 | require 'asana' 34 | 35 | 36 | client = Asana::Client.new do |c| 37 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 38 | end 39 | 40 | 41 | result = client.memberships.update_membership(membership_gid: 'membership_gid', field: "value", field: "value", options: {pretty: true}) 42 | -------------------------------------------------------------------------------- /samples/organization_exports_sample.yaml: -------------------------------------------------------------------------------- 1 | organizationexports: 2 | create_organization_export: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.organization_exports.create_organization_export(field: "value", field: "value", options: {pretty: true}) 12 | get_organization_export: >- 13 | require 'asana' 14 | 15 | 16 | client = Asana::Client.new do |c| 17 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 18 | end 19 | 20 | 21 | result = client.organization_exports.get_organization_export(organization_export_gid: 'organization_export_gid', param: "value", param: "value", options: {pretty: true}) 22 | -------------------------------------------------------------------------------- /samples/portfolio_memberships_sample.yaml: -------------------------------------------------------------------------------- 1 | portfoliomemberships: 2 | get_portfolio_membership: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.portfolio_memberships.get_portfolio_membership(portfolio_membership_gid: 'portfolio_membership_gid', param: "value", param: "value", options: {pretty: true}) 12 | get_portfolio_memberships: >- 13 | require 'asana' 14 | 15 | 16 | client = Asana::Client.new do |c| 17 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 18 | end 19 | 20 | 21 | result = client.portfolio_memberships.get_portfolio_memberships(param: "value", param: "value", options: {pretty: true}) 22 | get_portfolio_memberships_for_portfolio: >- 23 | require 'asana' 24 | 25 | 26 | client = Asana::Client.new do |c| 27 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 28 | end 29 | 30 | 31 | result = client.portfolio_memberships.get_portfolio_memberships_for_portfolio(portfolio_gid: 'portfolio_gid', param: "value", param: "value", options: {pretty: true}) 32 | -------------------------------------------------------------------------------- /samples/portfolios_sample.yaml: -------------------------------------------------------------------------------- 1 | portfolios: 2 | add_custom_field_setting_for_portfolio: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.portfolios.add_custom_field_setting_for_portfolio(portfolio_gid: 'portfolio_gid', field: "value", field: "value", options: {pretty: true}) 12 | add_item_for_portfolio: >- 13 | require 'asana' 14 | 15 | 16 | client = Asana::Client.new do |c| 17 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 18 | end 19 | 20 | 21 | result = client.portfolios.add_item_for_portfolio(portfolio_gid: 'portfolio_gid', field: "value", field: "value", options: {pretty: true}) 22 | add_members_for_portfolio: >- 23 | require 'asana' 24 | 25 | 26 | client = Asana::Client.new do |c| 27 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 28 | end 29 | 30 | 31 | result = client.portfolios.add_members_for_portfolio(portfolio_gid: 'portfolio_gid', field: "value", field: "value", options: {pretty: true}) 32 | create_portfolio: >- 33 | require 'asana' 34 | 35 | 36 | client = Asana::Client.new do |c| 37 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 38 | end 39 | 40 | 41 | result = client.portfolios.create_portfolio(field: "value", field: "value", options: {pretty: true}) 42 | delete_portfolio: >- 43 | require 'asana' 44 | 45 | 46 | client = Asana::Client.new do |c| 47 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 48 | end 49 | 50 | 51 | result = client.portfolios.delete_portfolio(portfolio_gid: 'portfolio_gid', options: {pretty: true}) 52 | get_items_for_portfolio: >- 53 | require 'asana' 54 | 55 | 56 | client = Asana::Client.new do |c| 57 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 58 | end 59 | 60 | 61 | result = client.portfolios.get_items_for_portfolio(portfolio_gid: 'portfolio_gid', param: "value", param: "value", options: {pretty: true}) 62 | get_portfolio: >- 63 | require 'asana' 64 | 65 | 66 | client = Asana::Client.new do |c| 67 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 68 | end 69 | 70 | 71 | result = client.portfolios.get_portfolio(portfolio_gid: 'portfolio_gid', param: "value", param: "value", options: {pretty: true}) 72 | get_portfolios: >- 73 | require 'asana' 74 | 75 | 76 | client = Asana::Client.new do |c| 77 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 78 | end 79 | 80 | 81 | result = client.portfolios.get_portfolios(workspace: ''workspace_example'', owner: ''owner_example'', param: "value", param: "value", options: {pretty: true}) 82 | remove_custom_field_setting_for_portfolio: >- 83 | require 'asana' 84 | 85 | 86 | client = Asana::Client.new do |c| 87 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 88 | end 89 | 90 | 91 | result = client.portfolios.remove_custom_field_setting_for_portfolio(portfolio_gid: 'portfolio_gid', field: "value", field: "value", options: {pretty: true}) 92 | remove_item_for_portfolio: >- 93 | require 'asana' 94 | 95 | 96 | client = Asana::Client.new do |c| 97 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 98 | end 99 | 100 | 101 | result = client.portfolios.remove_item_for_portfolio(portfolio_gid: 'portfolio_gid', field: "value", field: "value", options: {pretty: true}) 102 | remove_members_for_portfolio: >- 103 | require 'asana' 104 | 105 | 106 | client = Asana::Client.new do |c| 107 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 108 | end 109 | 110 | 111 | result = client.portfolios.remove_members_for_portfolio(portfolio_gid: 'portfolio_gid', field: "value", field: "value", options: {pretty: true}) 112 | update_portfolio: >- 113 | require 'asana' 114 | 115 | 116 | client = Asana::Client.new do |c| 117 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 118 | end 119 | 120 | 121 | result = client.portfolios.update_portfolio(portfolio_gid: 'portfolio_gid', field: "value", field: "value", options: {pretty: true}) 122 | -------------------------------------------------------------------------------- /samples/project_briefs_sample.yaml: -------------------------------------------------------------------------------- 1 | projectbriefs: 2 | create_project_brief: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.project_briefs.create_project_brief(project_gid: 'project_gid', field: "value", field: "value", options: {pretty: true}) 12 | delete_project_brief: >- 13 | require 'asana' 14 | 15 | 16 | client = Asana::Client.new do |c| 17 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 18 | end 19 | 20 | 21 | result = client.project_briefs.delete_project_brief(project_brief_gid: 'project_brief_gid', options: {pretty: true}) 22 | get_project_brief: >- 23 | require 'asana' 24 | 25 | 26 | client = Asana::Client.new do |c| 27 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 28 | end 29 | 30 | 31 | result = client.project_briefs.get_project_brief(project_brief_gid: 'project_brief_gid', param: "value", param: "value", options: {pretty: true}) 32 | update_project_brief: >- 33 | require 'asana' 34 | 35 | 36 | client = Asana::Client.new do |c| 37 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 38 | end 39 | 40 | 41 | result = client.project_briefs.update_project_brief(project_brief_gid: 'project_brief_gid', field: "value", field: "value", options: {pretty: true}) 42 | -------------------------------------------------------------------------------- /samples/project_memberships_sample.yaml: -------------------------------------------------------------------------------- 1 | projectmemberships: 2 | get_project_membership: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.project_memberships.get_project_membership(project_membership_gid: 'project_membership_gid', param: "value", param: "value", options: {pretty: true}) 12 | get_project_memberships_for_project: >- 13 | require 'asana' 14 | 15 | 16 | client = Asana::Client.new do |c| 17 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 18 | end 19 | 20 | 21 | result = client.project_memberships.get_project_memberships_for_project(project_gid: 'project_gid', param: "value", param: "value", options: {pretty: true}) 22 | -------------------------------------------------------------------------------- /samples/project_statuses_sample.yaml: -------------------------------------------------------------------------------- 1 | projectstatuses: 2 | create_project_status_for_project: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.project_statuses.create_project_status_for_project(project_gid: 'project_gid', field: "value", field: "value", options: {pretty: true}) 12 | delete_project_status: >- 13 | require 'asana' 14 | 15 | 16 | client = Asana::Client.new do |c| 17 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 18 | end 19 | 20 | 21 | result = client.project_statuses.delete_project_status(project_status_gid: 'project_status_gid', options: {pretty: true}) 22 | get_project_status: >- 23 | require 'asana' 24 | 25 | 26 | client = Asana::Client.new do |c| 27 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 28 | end 29 | 30 | 31 | result = client.project_statuses.get_project_status(project_status_gid: 'project_status_gid', param: "value", param: "value", options: {pretty: true}) 32 | get_project_statuses_for_project: >- 33 | require 'asana' 34 | 35 | 36 | client = Asana::Client.new do |c| 37 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 38 | end 39 | 40 | 41 | result = client.project_statuses.get_project_statuses_for_project(project_gid: 'project_gid', param: "value", param: "value", options: {pretty: true}) 42 | -------------------------------------------------------------------------------- /samples/project_templates_sample.yaml: -------------------------------------------------------------------------------- 1 | projecttemplates: 2 | get_project_template: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.project_templates.get_project_template(project_template_gid: 'project_template_gid', param: "value", param: "value", options: {pretty: true}) 12 | get_project_templates: >- 13 | require 'asana' 14 | 15 | 16 | client = Asana::Client.new do |c| 17 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 18 | end 19 | 20 | 21 | result = client.project_templates.get_project_templates(param: "value", param: "value", options: {pretty: true}) 22 | get_project_templates_for_team: >- 23 | require 'asana' 24 | 25 | 26 | client = Asana::Client.new do |c| 27 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 28 | end 29 | 30 | 31 | result = client.project_templates.get_project_templates_for_team(team_gid: 'team_gid', param: "value", param: "value", options: {pretty: true}) 32 | instantiate_project: >- 33 | require 'asana' 34 | 35 | 36 | client = Asana::Client.new do |c| 37 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 38 | end 39 | 40 | 41 | result = client.project_templates.instantiate_project(project_template_gid: 'project_template_gid', field: "value", field: "value", options: {pretty: true}) 42 | -------------------------------------------------------------------------------- /samples/sections_sample.yaml: -------------------------------------------------------------------------------- 1 | sections: 2 | add_task_for_section: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.sections.add_task_for_section(section_gid: 'section_gid', field: "value", field: "value", options: {pretty: true}) 12 | create_section_for_project: >- 13 | require 'asana' 14 | 15 | 16 | client = Asana::Client.new do |c| 17 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 18 | end 19 | 20 | 21 | result = client.sections.create_section_for_project(project_gid: 'project_gid', field: "value", field: "value", options: {pretty: true}) 22 | delete_section: >- 23 | require 'asana' 24 | 25 | 26 | client = Asana::Client.new do |c| 27 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 28 | end 29 | 30 | 31 | result = client.sections.delete_section(section_gid: 'section_gid', options: {pretty: true}) 32 | get_section: >- 33 | require 'asana' 34 | 35 | 36 | client = Asana::Client.new do |c| 37 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 38 | end 39 | 40 | 41 | result = client.sections.get_section(section_gid: 'section_gid', param: "value", param: "value", options: {pretty: true}) 42 | get_sections_for_project: >- 43 | require 'asana' 44 | 45 | 46 | client = Asana::Client.new do |c| 47 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 48 | end 49 | 50 | 51 | result = client.sections.get_sections_for_project(project_gid: 'project_gid', param: "value", param: "value", options: {pretty: true}) 52 | insert_section_for_project: >- 53 | require 'asana' 54 | 55 | 56 | client = Asana::Client.new do |c| 57 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 58 | end 59 | 60 | 61 | result = client.sections.insert_section_for_project(project_gid: 'project_gid', field: "value", field: "value", options: {pretty: true}) 62 | update_section: >- 63 | require 'asana' 64 | 65 | 66 | client = Asana::Client.new do |c| 67 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 68 | end 69 | 70 | 71 | result = client.sections.update_section(section_gid: 'section_gid', field: "value", field: "value", options: {pretty: true}) 72 | -------------------------------------------------------------------------------- /samples/status_updates_sample.yaml: -------------------------------------------------------------------------------- 1 | statusupdates: 2 | create_status_for_object: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.status_updates.create_status_for_object(field: "value", field: "value", options: {pretty: true}) 12 | delete_status: >- 13 | require 'asana' 14 | 15 | 16 | client = Asana::Client.new do |c| 17 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 18 | end 19 | 20 | 21 | result = client.status_updates.delete_status(status_gid: 'status_gid', options: {pretty: true}) 22 | get_status: >- 23 | require 'asana' 24 | 25 | 26 | client = Asana::Client.new do |c| 27 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 28 | end 29 | 30 | 31 | result = client.status_updates.get_status(status_gid: 'status_gid', param: "value", param: "value", options: {pretty: true}) 32 | get_statuses_for_object: >- 33 | require 'asana' 34 | 35 | 36 | client = Asana::Client.new do |c| 37 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 38 | end 39 | 40 | 41 | result = client.status_updates.get_statuses_for_object(parent: ''parent_example'', param: "value", param: "value", options: {pretty: true}) 42 | -------------------------------------------------------------------------------- /samples/stories_sample.yaml: -------------------------------------------------------------------------------- 1 | stories: 2 | create_story_for_task: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.stories.create_story_for_task(task_gid: 'task_gid', field: "value", field: "value", options: {pretty: true}) 12 | delete_story: >- 13 | require 'asana' 14 | 15 | 16 | client = Asana::Client.new do |c| 17 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 18 | end 19 | 20 | 21 | result = client.stories.delete_story(story_gid: 'story_gid', options: {pretty: true}) 22 | get_stories_for_task: >- 23 | require 'asana' 24 | 25 | 26 | client = Asana::Client.new do |c| 27 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 28 | end 29 | 30 | 31 | result = client.stories.get_stories_for_task(task_gid: 'task_gid', param: "value", param: "value", options: {pretty: true}) 32 | get_story: >- 33 | require 'asana' 34 | 35 | 36 | client = Asana::Client.new do |c| 37 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 38 | end 39 | 40 | 41 | result = client.stories.get_story(story_gid: 'story_gid', param: "value", param: "value", options: {pretty: true}) 42 | update_story: >- 43 | require 'asana' 44 | 45 | 46 | client = Asana::Client.new do |c| 47 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 48 | end 49 | 50 | 51 | result = client.stories.update_story(story_gid: 'story_gid', field: "value", field: "value", options: {pretty: true}) 52 | -------------------------------------------------------------------------------- /samples/tags_sample.yaml: -------------------------------------------------------------------------------- 1 | tags: 2 | create_tag: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.tags.create_tag(field: "value", field: "value", options: {pretty: true}) 12 | create_tag_for_workspace: >- 13 | require 'asana' 14 | 15 | 16 | client = Asana::Client.new do |c| 17 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 18 | end 19 | 20 | 21 | result = client.tags.create_tag_for_workspace(workspace_gid: 'workspace_gid', field: "value", field: "value", options: {pretty: true}) 22 | delete_tag: >- 23 | require 'asana' 24 | 25 | 26 | client = Asana::Client.new do |c| 27 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 28 | end 29 | 30 | 31 | result = client.tags.delete_tag(tag_gid: 'tag_gid', options: {pretty: true}) 32 | get_tag: >- 33 | require 'asana' 34 | 35 | 36 | client = Asana::Client.new do |c| 37 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 38 | end 39 | 40 | 41 | result = client.tags.get_tag(tag_gid: 'tag_gid', param: "value", param: "value", options: {pretty: true}) 42 | get_tags: >- 43 | require 'asana' 44 | 45 | 46 | client = Asana::Client.new do |c| 47 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 48 | end 49 | 50 | 51 | result = client.tags.get_tags(param: "value", param: "value", options: {pretty: true}) 52 | get_tags_for_task: >- 53 | require 'asana' 54 | 55 | 56 | client = Asana::Client.new do |c| 57 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 58 | end 59 | 60 | 61 | result = client.tags.get_tags_for_task(task_gid: 'task_gid', param: "value", param: "value", options: {pretty: true}) 62 | get_tags_for_workspace: >- 63 | require 'asana' 64 | 65 | 66 | client = Asana::Client.new do |c| 67 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 68 | end 69 | 70 | 71 | result = client.tags.get_tags_for_workspace(workspace_gid: 'workspace_gid', param: "value", param: "value", options: {pretty: true}) 72 | update_tag: >- 73 | require 'asana' 74 | 75 | 76 | client = Asana::Client.new do |c| 77 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 78 | end 79 | 80 | 81 | result = client.tags.update_tag(tag_gid: 'tag_gid', field: "value", field: "value", options: {pretty: true}) 82 | -------------------------------------------------------------------------------- /samples/team_memberships_sample.yaml: -------------------------------------------------------------------------------- 1 | teammemberships: 2 | get_team_membership: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.team_memberships.get_team_membership(team_membership_gid: 'team_membership_gid', param: "value", param: "value", options: {pretty: true}) 12 | get_team_memberships: >- 13 | require 'asana' 14 | 15 | 16 | client = Asana::Client.new do |c| 17 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 18 | end 19 | 20 | 21 | result = client.team_memberships.get_team_memberships(param: "value", param: "value", options: {pretty: true}) 22 | get_team_memberships_for_team: >- 23 | require 'asana' 24 | 25 | 26 | client = Asana::Client.new do |c| 27 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 28 | end 29 | 30 | 31 | result = client.team_memberships.get_team_memberships_for_team(team_gid: 'team_gid', param: "value", param: "value", options: {pretty: true}) 32 | get_team_memberships_for_user: >- 33 | require 'asana' 34 | 35 | 36 | client = Asana::Client.new do |c| 37 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 38 | end 39 | 40 | 41 | result = client.team_memberships.get_team_memberships_for_user(user_gid: 'user_gid', workspace: ''workspace_example'', param: "value", param: "value", options: {pretty: true}) 42 | -------------------------------------------------------------------------------- /samples/teams_sample.yaml: -------------------------------------------------------------------------------- 1 | teams: 2 | add_user_for_team: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.teams.add_user_for_team(team_gid: 'team_gid', field: "value", field: "value", options: {pretty: true}) 12 | create_team: >- 13 | require 'asana' 14 | 15 | 16 | client = Asana::Client.new do |c| 17 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 18 | end 19 | 20 | 21 | result = client.teams.create_team(field: "value", field: "value", options: {pretty: true}) 22 | get_team: >- 23 | require 'asana' 24 | 25 | 26 | client = Asana::Client.new do |c| 27 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 28 | end 29 | 30 | 31 | result = client.teams.get_team(team_gid: 'team_gid', param: "value", param: "value", options: {pretty: true}) 32 | get_teams_for_user: >- 33 | require 'asana' 34 | 35 | 36 | client = Asana::Client.new do |c| 37 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 38 | end 39 | 40 | 41 | result = client.teams.get_teams_for_user(user_gid: 'user_gid', organization: ''organization_example'', param: "value", param: "value", options: {pretty: true}) 42 | get_teams_for_workspace: >- 43 | require 'asana' 44 | 45 | 46 | client = Asana::Client.new do |c| 47 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 48 | end 49 | 50 | 51 | result = client.teams.get_teams_for_workspace(workspace_gid: 'workspace_gid', param: "value", param: "value", options: {pretty: true}) 52 | remove_user_for_team: >- 53 | require 'asana' 54 | 55 | 56 | client = Asana::Client.new do |c| 57 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 58 | end 59 | 60 | 61 | result = client.teams.remove_user_for_team(team_gid: 'team_gid', field: "value", field: "value", options: {pretty: true}) 62 | update_team: >- 63 | require 'asana' 64 | 65 | 66 | client = Asana::Client.new do |c| 67 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 68 | end 69 | 70 | 71 | result = client.teams.update_team(field: "value", field: "value", options: {pretty: true}) 72 | -------------------------------------------------------------------------------- /samples/time_periods_sample.yaml: -------------------------------------------------------------------------------- 1 | timeperiods: 2 | get_time_period: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.time_periods.get_time_period(time_period_gid: 'time_period_gid', param: "value", param: "value", options: {pretty: true}) 12 | get_time_periods: >- 13 | require 'asana' 14 | 15 | 16 | client = Asana::Client.new do |c| 17 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 18 | end 19 | 20 | 21 | result = client.time_periods.get_time_periods(workspace: ''workspace_example'', param: "value", param: "value", options: {pretty: true}) 22 | -------------------------------------------------------------------------------- /samples/typeahead_sample.yaml: -------------------------------------------------------------------------------- 1 | typeahead: 2 | typeahead_for_workspace: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.typeahead.typeahead_for_workspace(workspace_gid: 'workspace_gid', resource_type: ''resource_type_example'', param: "value", param: "value", options: {pretty: true}) 12 | -------------------------------------------------------------------------------- /samples/user_task_lists_sample.yaml: -------------------------------------------------------------------------------- 1 | usertasklists: 2 | get_user_task_list: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.user_task_lists.get_user_task_list(user_task_list_gid: 'user_task_list_gid', param: "value", param: "value", options: {pretty: true}) 12 | get_user_task_list_for_user: >- 13 | require 'asana' 14 | 15 | 16 | client = Asana::Client.new do |c| 17 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 18 | end 19 | 20 | 21 | result = client.user_task_lists.get_user_task_list_for_user(user_gid: 'user_gid', workspace: ''workspace_example'', param: "value", param: "value", options: {pretty: true}) 22 | -------------------------------------------------------------------------------- /samples/users_sample.yaml: -------------------------------------------------------------------------------- 1 | users: 2 | get_favorites_for_user: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.users.get_favorites_for_user(user_gid: 'user_gid', resource_type: ''resource_type_example'', workspace: ''workspace_example'', param: "value", param: "value", options: {pretty: true}) 12 | get_user: >- 13 | require 'asana' 14 | 15 | 16 | client = Asana::Client.new do |c| 17 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 18 | end 19 | 20 | 21 | result = client.users.get_user(user_gid: 'user_gid', param: "value", param: "value", options: {pretty: true}) 22 | get_users: >- 23 | require 'asana' 24 | 25 | 26 | client = Asana::Client.new do |c| 27 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 28 | end 29 | 30 | 31 | result = client.users.get_users(param: "value", param: "value", options: {pretty: true}) 32 | get_users_for_team: >- 33 | require 'asana' 34 | 35 | 36 | client = Asana::Client.new do |c| 37 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 38 | end 39 | 40 | 41 | result = client.users.get_users_for_team(team_gid: 'team_gid', param: "value", param: "value", options: {pretty: true}) 42 | get_users_for_workspace: >- 43 | require 'asana' 44 | 45 | 46 | client = Asana::Client.new do |c| 47 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 48 | end 49 | 50 | 51 | result = client.users.get_users_for_workspace(workspace_gid: 'workspace_gid', param: "value", param: "value", options: {pretty: true}) 52 | -------------------------------------------------------------------------------- /samples/webhooks_sample.yaml: -------------------------------------------------------------------------------- 1 | webhooks: 2 | create_webhook: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.webhooks.create_webhook(field: "value", field: "value", options: {pretty: true}) 12 | delete_webhook: >- 13 | require 'asana' 14 | 15 | 16 | client = Asana::Client.new do |c| 17 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 18 | end 19 | 20 | 21 | result = client.webhooks.delete_webhook(webhook_gid: 'webhook_gid', options: {pretty: true}) 22 | get_webhook: >- 23 | require 'asana' 24 | 25 | 26 | client = Asana::Client.new do |c| 27 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 28 | end 29 | 30 | 31 | result = client.webhooks.get_webhook(webhook_gid: 'webhook_gid', param: "value", param: "value", options: {pretty: true}) 32 | get_webhooks: >- 33 | require 'asana' 34 | 35 | 36 | client = Asana::Client.new do |c| 37 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 38 | end 39 | 40 | 41 | result = client.webhooks.get_webhooks(workspace: ''workspace_example'', param: "value", param: "value", options: {pretty: true}) 42 | update_webhook: >- 43 | require 'asana' 44 | 45 | 46 | client = Asana::Client.new do |c| 47 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 48 | end 49 | 50 | 51 | result = client.webhooks.update_webhook(webhook_gid: 'webhook_gid', field: "value", field: "value", options: {pretty: true}) 52 | -------------------------------------------------------------------------------- /samples/workspace_memberships_sample.yaml: -------------------------------------------------------------------------------- 1 | workspacememberships: 2 | get_workspace_membership: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.workspace_memberships.get_workspace_membership(workspace_membership_gid: 'workspace_membership_gid', param: "value", param: "value", options: {pretty: true}) 12 | get_workspace_memberships_for_user: >- 13 | require 'asana' 14 | 15 | 16 | client = Asana::Client.new do |c| 17 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 18 | end 19 | 20 | 21 | result = client.workspace_memberships.get_workspace_memberships_for_user(user_gid: 'user_gid', param: "value", param: "value", options: {pretty: true}) 22 | get_workspace_memberships_for_workspace: >- 23 | require 'asana' 24 | 25 | 26 | client = Asana::Client.new do |c| 27 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 28 | end 29 | 30 | 31 | result = client.workspace_memberships.get_workspace_memberships_for_workspace(workspace_gid: 'workspace_gid', param: "value", param: "value", options: {pretty: true}) 32 | -------------------------------------------------------------------------------- /samples/workspaces_sample.yaml: -------------------------------------------------------------------------------- 1 | workspaces: 2 | add_user_for_workspace: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.workspaces.add_user_for_workspace(workspace_gid: 'workspace_gid', field: "value", field: "value", options: {pretty: true}) 12 | get_workspace: >- 13 | require 'asana' 14 | 15 | 16 | client = Asana::Client.new do |c| 17 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 18 | end 19 | 20 | 21 | result = client.workspaces.get_workspace(workspace_gid: 'workspace_gid', param: "value", param: "value", options: {pretty: true}) 22 | get_workspaces: >- 23 | require 'asana' 24 | 25 | 26 | client = Asana::Client.new do |c| 27 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 28 | end 29 | 30 | 31 | result = client.workspaces.get_workspaces(param: "value", param: "value", options: {pretty: true}) 32 | remove_user_for_workspace: >- 33 | require 'asana' 34 | 35 | 36 | client = Asana::Client.new do |c| 37 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 38 | end 39 | 40 | 41 | result = client.workspaces.remove_user_for_workspace(workspace_gid: 'workspace_gid', field: "value", field: "value", options: {pretty: true}) 42 | update_workspace: >- 43 | require 'asana' 44 | 45 | 46 | client = Asana::Client.new do |c| 47 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 48 | end 49 | 50 | 51 | result = client.workspaces.update_workspace(workspace_gid: 'workspace_gid', field: "value", field: "value", options: {pretty: true}) 52 | -------------------------------------------------------------------------------- /spec/asana/authentication/oauth2/access_token_authentication_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe Asana::Authentication::OAuth2::AccessTokenAuthentication do 4 | describe described_class do 5 | let(:auth) { described_class.new(token) } 6 | 7 | context 'if the token is not expired' do 8 | let(:token) do 9 | instance_double(OAuth2::AccessToken, expired?: false, token: 'TOKEN') 10 | end 11 | 12 | it 'configures Faraday to use OAuth2 with an access token' do 13 | allow(token).to receive(:refresh!) 14 | conn = Faraday.new do |builder| 15 | auth.configure(builder) 16 | end 17 | expect(token).not_to have_received(:refresh!) 18 | expect(conn.builder.handlers).to include(Faraday::Request::Authorization) 19 | end 20 | end 21 | 22 | context 'if the token is expired' do 23 | let(:token) do 24 | instance_double(OAuth2::AccessToken, expired?: true, token: 'TOKEN') 25 | end 26 | 27 | it 'refreshes the token and uses the new one' do 28 | allow(token).to receive(:refresh!) { token } 29 | conn = Faraday.new do |builder| 30 | auth.configure(builder) 31 | end 32 | expect(token).to have_received(:refresh!) 33 | expect(conn.builder.handlers).to include(Faraday::Request::Authorization) 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/asana/authentication/oauth2/bearer_token_authentication_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe Asana::Authentication::OAuth2::BearerTokenAuthentication do 4 | let(:token) { 'MYTOKEN' } 5 | let(:auth) { described_class.new(token) } 6 | let(:conn) do 7 | Faraday.new do |builder| 8 | auth.configure(builder) 9 | end 10 | end 11 | 12 | it 'configures Faraday to use OAuth2 authentication with a bearer token' do 13 | expect(conn.builder.handlers).to include(Faraday::Request::Authorization) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/asana/authentication/oauth2/client_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe Asana::Authentication::OAuth2::Client do 4 | let(:client) do 5 | described_class.new(client_id: 'CLIENT_ID', 6 | client_secret: 'CLIENT_SECRET', 7 | redirect_uri: 'http://redirect_uri.com') 8 | end 9 | 10 | describe '#authorize_url' do 11 | it 'returns the OAuth2 authorize url' do 12 | expect(client.authorize_url) 13 | .to eq('https://app.asana.com/-/oauth_authorize?client_id=CLIENT_ID' \ 14 | '&redirect_uri=http%3A%2F%2Fredirect_uri.com&response_type=code') 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/asana/authentication/token_authentication_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe Asana::Authentication::TokenAuthentication do 4 | describe described_class do 5 | let(:auth) { described_class.new('MYTOKEN') } 6 | 7 | it 'configures Faraday to use basic authentication with a token' do 8 | conn = Faraday.new do |builder| 9 | auth.configure(builder) 10 | end 11 | expect(conn.builder.handlers).to include(Faraday::Request::Authorization) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/asana/client/configuration_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe Asana::Client::Configuration do 4 | describe '#authentication' do 5 | context 'with :oauth2' do 6 | context 'and an ::OAuth2::AccessToken object' do 7 | it 'sets authentication with an OAuth2 access token' do 8 | auth = described_class.new.tap do |config| 9 | config.authentication :oauth2, 10 | OAuth2::AccessToken.new(nil, 'token') 11 | end.to_h[:authentication] 12 | 13 | expect(auth) 14 | .to be_a(Asana::Authentication::OAuth2::AccessTokenAuthentication) 15 | end 16 | end 17 | 18 | context 'and a hash with a :refresh_token' do 19 | context 'and valid client credentials' do 20 | it 'sets authentication with an OAuth2 access token' do 21 | # rubocop:disable RSpec/AnyInstance 22 | expect_any_instance_of(Asana::Authentication::OAuth2::Client) 23 | .to receive(:token_from_refresh_token) 24 | .with('refresh_token') do 25 | OAuth2::AccessToken.new(nil, 'token') 26 | end 27 | # rubocop:enable RSpec/AnyInstance 28 | 29 | auth = described_class.new.tap do |config| 30 | config.authentication :oauth2, 31 | refresh_token: 'refresh_token', 32 | client_id: 'client_id', 33 | client_secret: 'client_id', 34 | redirect_uri: 'http://redirect_uri' 35 | end.to_h[:authentication] 36 | 37 | expect(auth) 38 | .to be_a(Asana::Authentication::OAuth2::AccessTokenAuthentication) 39 | end 40 | end 41 | 42 | context 'and incomplete client credentials' do 43 | it 'fails with an error' do 44 | expect do 45 | described_class.new.tap do |config| 46 | config.authentication :oauth2, 47 | refresh_token: 'refresh_token', 48 | client_id: 'client_id' 49 | end 50 | end.to raise_error(ArgumentError, /missing/i) 51 | end 52 | end 53 | end 54 | 55 | context 'and a hash with a :bearer_token' do 56 | it 'sets authentication with an OAuth2 bearer token' do 57 | auth = described_class.new.tap do |config| 58 | config.authentication :oauth2, bearer_token: 'token' 59 | end.to_h[:authentication] 60 | 61 | expect(auth) 62 | .to be_a(Asana::Authentication::OAuth2::BearerTokenAuthentication) 63 | end 64 | end 65 | end 66 | 67 | context 'with :access_token' do 68 | it 'sets authentication with an API token' do 69 | auth = described_class.new.tap do |config| 70 | config.authentication :access_token, 'token' 71 | end.to_h[:authentication] 72 | 73 | expect(auth).to be_a(Asana::Authentication::OAuth2::BearerTokenAuthentication) 74 | end 75 | end 76 | end 77 | 78 | describe '#faraday_adapter' do 79 | it 'sets a custom faraday adapter for the HTTP requests' do 80 | adapter = described_class.new.tap do |config| 81 | config.faraday_adapter :typhoeus 82 | end.to_h[:faraday_adapter] 83 | 84 | expect(adapter).to eq(:typhoeus) 85 | end 86 | end 87 | 88 | describe '#configure_faraday' do 89 | it 'passes in a custom configuration block for the Faraday connection' do 90 | faraday_config = described_class.new.tap do |config| 91 | config.configure_faraday do |conn| 92 | conn.use :some_middleware 93 | end 94 | end.to_h[:faraday_configuration] 95 | 96 | expect(faraday_config).to be_a(Proc) 97 | end 98 | end 99 | 100 | describe '#debug_mode' do 101 | it 'configures the client to be more verbose' do 102 | debug_mode = described_class.new.tap(&:debug_mode).to_h[:debug_mode] 103 | expect(debug_mode).to be(true) 104 | end 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /spec/asana/client_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'support/stub_api' 4 | 5 | RSpec.describe Asana::Client do 6 | let(:api) { StubAPI.new } 7 | let(:client) do 8 | described_class.new do |c| 9 | c.authentication :access_token, 'foo' 10 | c.faraday_adapter api.adapter 11 | end 12 | end 13 | 14 | context 'exposes HTTP verbs to interact with the API at a lower level' do 15 | specify '#get' do 16 | api.on(:get, '/users/me') do |response| 17 | response.body = { data: { foo: 'bar' } } 18 | end 19 | 20 | expect(client.get('/users/me').body).to eq('data' => { 'foo' => 'bar' }) 21 | end 22 | 23 | specify '#post' do 24 | api.on(:post, '/tags', data: { name: 'work' }) do |response| 25 | response.body = { data: { foo: 'bar' } } 26 | end 27 | 28 | expect(client.post('/tags', body: { name: 'work' }).body) 29 | .to eq('data' => { 'foo' => 'bar' }) 30 | end 31 | 32 | specify '#put' do 33 | api.on(:put, '/tags/1', data: { name: 'work' }) do |response| 34 | response.body = { data: { foo: 'bar' } } 35 | end 36 | 37 | expect(client.put('/tags/1', body: { name: 'work' }).body) 38 | .to eq('data' => { 'foo' => 'bar' }) 39 | end 40 | 41 | specify '#delete' do 42 | api.on(:delete, '/tags/1') do |response| 43 | response.body = { data: {} } 44 | end 45 | 46 | expect(client.delete('/tags/1').body) 47 | .to eq('data' => {}) 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/asana/http_client/error_handling_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe Asana::HttpClient::ErrorHandling do 4 | describe '.handle' do 5 | def failed_response(status, headers: {}, body: {}) 6 | lambda do 7 | raise Faraday::ClientError.new(nil, status: status, 8 | body: JSON.dump(body), 9 | headers: headers) 10 | end 11 | end 12 | 13 | context 'when the response is successful' do 14 | it 'returns the result of the block' do 15 | expect(described_class.handle { 3 }).to eq(3) 16 | end 17 | end 18 | 19 | context 'when the response has status 400' do 20 | let(:request) do 21 | body = { 'errors' => [{ 'message' => 'Invalid field' }] } 22 | failed_response(400, body: body) 23 | end 24 | 25 | it 'raises an InvalidRequest exception containing the errors' do 26 | expect { described_class.handle(&request) }.to raise_error do |error| 27 | expect(error).to be_a(Asana::Errors::InvalidRequest) 28 | expect(error.errors).to eq(['Invalid field']) 29 | end 30 | end 31 | end 32 | 33 | context 'when the response has status 401' do 34 | let(:request) { failed_response(401) } 35 | 36 | it 'raises a NotAuthorized exception' do 37 | expect { described_class.handle(&request) } 38 | .to raise_error(Asana::Errors::NotAuthorized) 39 | end 40 | end 41 | 42 | context 'when the response has status 403' do 43 | let(:request) { failed_response(403) } 44 | 45 | it 'raises a Forbidden exception' do 46 | expect { described_class.handle(&request) } 47 | .to raise_error(Asana::Errors::Forbidden) 48 | end 49 | end 50 | 51 | context 'when the response has status 404' do 52 | let(:request) { failed_response(404) } 53 | 54 | it 'raises a NotFound exception' do 55 | expect { described_class.handle(&request) } 56 | .to raise_error(Asana::Errors::NotFound) 57 | end 58 | end 59 | 60 | context 'when the response has status 429' do 61 | let(:request) { failed_response(429, headers: { 'Retry-After' => 20 }) } 62 | 63 | it 'raises a RateLimitEnforced exception with seconds to wait' do 64 | expect { described_class.handle(&request) }.to raise_error do |error| 65 | expect(error).to be_a(Asana::Errors::RateLimitEnforced) 66 | expect(error.retry_after_seconds).to eq(20) 67 | end 68 | end 69 | end 70 | 71 | context 'when the response has status 500' do 72 | let(:request) do 73 | body = { 'errors' => [{ 'phrase' => 'A quick lazy dog jumps' }] } 74 | failed_response(500, body: body) 75 | end 76 | 77 | it 'raises a ServerError exception with a unique phrase' do 78 | expect { described_class.handle(&request) }.to raise_error do |error| 79 | expect(error).to be_a(Asana::Errors::ServerError) 80 | expect(error.phrase).to eq('A quick lazy dog jumps') 81 | end 82 | end 83 | end 84 | 85 | context 'when the response fails with whatever other status' do 86 | let(:request) { failed_response(510) } 87 | 88 | it 'raises a generic APIError exception' do 89 | expect { described_class.handle(&request) } 90 | .to raise_error(Asana::Errors::APIError) 91 | end 92 | end 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /spec/asana/http_client_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'support/stub_api' 4 | 5 | RSpec.describe Asana::HttpClient do 6 | let(:api) { StubAPI.new } 7 | let(:auth) { Asana::Authentication::TokenAuthentication.new('foo') } 8 | let(:client) do 9 | described_class.new(authentication: auth, adapter: api.to_proc) 10 | end 11 | 12 | describe '#get' do 13 | it 'performs a GET request against the Asana API' do 14 | api.on(:get, '/users/me') do |response| 15 | response.body = { user: 'foo' } 16 | end 17 | 18 | client.get('/users/me').tap do |response| 19 | expect(response.status).to eq(200) 20 | expect(response.body).to eq('user' => 'foo') 21 | end 22 | end 23 | 24 | it 'accepts I/O options' do 25 | api.on(:get, '/users/me?opt_pretty=true') do |response| 26 | response.body = { user: 'foo' } 27 | end 28 | 29 | client.get('/users/me', options: { pretty: true }).tap do |response| 30 | expect(response.status).to eq(200) 31 | expect(response.body).to eq('user' => 'foo') 32 | end 33 | end 34 | 35 | it 'accepts I/O options containing arrays' do 36 | api.on(:get, '/users/me?opt_fields=foo,bar') do |response| 37 | response.body = { user: 'foo' } 38 | end 39 | 40 | client.get('/users/me', 41 | options: { fields: %w[foo bar] }).tap do |response| 42 | expect(response.status).to eq(200) 43 | expect(response.body).to eq('user' => 'foo') 44 | end 45 | end 46 | end 47 | 48 | describe '#put' do 49 | it 'performs a PUT request against the Asana API' do 50 | api.on(:put, '/users/me', 'data' => { 'name' => 'John' }) do |response| 51 | response.body = { user: 'foo' } 52 | end 53 | 54 | client.put('/users/me', body: { 'name' => 'John' }).tap do |response| 55 | expect(response.status).to eq(200) 56 | expect(response.body).to eq('user' => 'foo') 57 | end 58 | end 59 | 60 | it 'accepts I/O options' do 61 | api.on(:put, '/users/me', 62 | 'data' => { 'name' => 'John' }, 63 | 'options' => { 'fields' => %w[foo bar] }) do |response| 64 | response.body = { user: 'foo' } 65 | end 66 | 67 | client.put('/users/me', 68 | body: { 'name' => 'John' }, 69 | options: { fields: %w[foo bar] }).tap do |response| 70 | expect(response.status).to eq(200) 71 | expect(response.body).to eq('user' => 'foo') 72 | end 73 | end 74 | end 75 | 76 | describe '#post' do 77 | it 'performs a POST request against the Asana API' do 78 | api.on(:post, '/users/me', 'data' => { 'name' => 'John' }) do |response| 79 | response.body = { user: 'foo' } 80 | end 81 | 82 | client.post('/users/me', body: { 'name' => 'John' }).tap do |response| 83 | expect(response.status).to eq(200) 84 | expect(response.body).to eq('user' => 'foo') 85 | end 86 | end 87 | 88 | it 'accepts I/O options' do 89 | api.on(:post, '/users/me', 90 | 'data' => { 'name' => 'John' }, 91 | 'options' => { 'fields' => %w[foo bar] }) do |response| 92 | response.body = { user: 'foo' } 93 | end 94 | 95 | client.post('/users/me', 96 | body: { 'name' => 'John' }, 97 | options: { fields: %w[foo bar] }).tap do |response| 98 | expect(response.status).to eq(200) 99 | expect(response.body).to eq('user' => 'foo') 100 | end 101 | end 102 | end 103 | 104 | describe '#delete' do 105 | it 'performs a DELETE request against the Asana API' do 106 | api.on(:delete, '/users/me') do |response| 107 | response.body = {} 108 | end 109 | 110 | client.delete('/users/me').tap do |response| 111 | expect(response.status).to eq(200) 112 | expect(response.body).to eq({}) 113 | end 114 | end 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /spec/asana/resources/attachment_uploading_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'support/stub_api' 4 | require 'support/resources_helper' 5 | 6 | RSpec.describe Asana::Resources::AttachmentUploading do 7 | mod = described_class 8 | 9 | let(:api) { StubAPI.new } 10 | let!(:unicorn_class) do 11 | defresource 'Unicorn' do 12 | include mod 13 | 14 | attr_reader :gid 15 | 16 | def self.plural_name 17 | 'unicorns' 18 | end 19 | end 20 | end 21 | let(:authentication) do 22 | Asana::Authentication::TokenAuthentication.new('token') 23 | end 24 | let(:client) do 25 | Asana::HttpClient.new(authentication: authentication, adapter: api.adapter) 26 | end 27 | 28 | include ResourcesHelper 29 | 30 | describe '#attach' do 31 | let(:arg_matcher) { ->(body) { body.is_a?(Faraday::CompositeReadIO) } } 32 | let(:unicorn) { unicorn_class.new({ gid: '1' }, client: client) } 33 | 34 | before do 35 | api.on(:post, '/unicorns/1/attachments', arg_matcher) do |response| 36 | response.body = { data: { gid: '10' } } 37 | end 38 | end 39 | 40 | context 'with a file from the file system' do 41 | it 'uploads an attachment to a unicorn' do 42 | attachment = unicorn.attach(filename: __FILE__, 43 | mime: 'image/jpg') 44 | 45 | expect(attachment).to be_a(Asana::Resources::Attachment) 46 | expect(attachment.gid).to eq('10') 47 | end 48 | end 49 | 50 | context 'with an IO' do 51 | let(:io) do 52 | StringIO.new(<<~CSV) 53 | Employee;Salary 54 | "Bill Lumbergh";70000 55 | "Peter Gibbons";40000 56 | CSV 57 | end 58 | 59 | it 'uploads an attachment to a unicorn' do 60 | attachment = unicorn.attach(io: io, 61 | mime: 'text/csv', 62 | filename: 'salaries.csv') 63 | 64 | expect(attachment).to be_a(Asana::Resources::Attachment) 65 | expect(attachment.gid).to eq('10') 66 | end 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /spec/asana/resources/events_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'support/stub_api' 4 | require 'support/resources_helper' 5 | 6 | RSpec.describe Asana::Resources::Events do 7 | let(:api) { StubAPI.new } 8 | let!(:unicorn_class) do 9 | defresource 'Unicorn' do 10 | include Asana::Resources::EventSubscription 11 | attr_reader :gid 12 | end 13 | end 14 | let(:unicorn) do 15 | unicorn_class.new({ gid: '1' }, client: client) 16 | end 17 | let(:first_batch) do 18 | [ 19 | { type: 'unicorn', action: 'born' }, 20 | { type: 'unicorn', action: 'first_words' } 21 | ] 22 | end 23 | let(:second_batch) { [] } 24 | let(:third_batch) do 25 | [{ type: 'unicorn', action: 'learned_to_fly' }] 26 | end 27 | let(:authentication) do 28 | Asana::Authentication::TokenAuthentication.new('token') 29 | end 30 | let(:client) do 31 | Asana::HttpClient.new(authentication: authentication, adapter: api.adapter) 32 | end 33 | 34 | include ResourcesHelper 35 | 36 | it 'is an infinite collection of events on a resource' do 37 | api.on(:get, '/events', resource: '1') do |response| 38 | response.body = { sync: 'firstsynctoken', 39 | data: first_batch } 40 | end 41 | 42 | api.on(:get, '/events', resource: '1', sync: 'firstsynctoken') do |response| 43 | response.body = { sync: 'secondsynctoken', 44 | data: second_batch } 45 | end 46 | 47 | api.on(:get, '/events', resource: '1', sync: 'secondsynctoken') do |response| 48 | response.body = { sync: 'thirdsynctoken', 49 | data: third_batch } 50 | end 51 | 52 | events = described_class.new(resource: unicorn.gid, 53 | client: client, 54 | wait: 0).take(3) 55 | expect(events.all? { |e| e.is_a?(Asana::Resources::Event) }).to be(true) 56 | expect(events.all? { |e| e.type == 'unicorn' }).to be(true) 57 | expect(events.map(&:action)).to eq(%w[born first_words learned_to_fly]) 58 | end 59 | 60 | it 'allows to fetch events about oneself with EventSubscription' do 61 | api.on(:get, '/events', resource: 1) do |response| 62 | response.body = { sync: 'firstsynctoken', 63 | data: first_batch } 64 | end 65 | 66 | expect(unicorn.events.first.action).to eq('born') 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /spec/asana/resources/resource_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'support/stub_api' 4 | require 'support/resources_helper' 5 | 6 | RSpec.describe Asana::Resources::Resource do 7 | let(:api) { StubAPI.new } 8 | let!(:unicorn_class) do 9 | defresource 'Unicorn' do 10 | def self.find_by_id(client, id) 11 | new({ 'gid' => id }, client: client) 12 | end 13 | end 14 | end 15 | let(:authentication) do 16 | Asana::Authentication::TokenAuthentication.new('token') 17 | end 18 | let(:client) do 19 | Asana::HttpClient.new(authentication: authentication, adapter: api.adapter) 20 | end 21 | 22 | include ResourcesHelper 23 | 24 | it 'auto-vivifies plain properties of the resource' do 25 | unicorn = unicorn_class.new({ 'name' => 'John' }, client: client) 26 | expect(unicorn.name).to eq('John') 27 | end 28 | 29 | it 'wraps hash values into Resources' do 30 | unicorn = unicorn_class.new({ 'friend' => { 'gid' => '1' } }, client: client) 31 | expect(unicorn.friend).to be_a(described_class) 32 | expect(unicorn.friend.gid).to eq('1') 33 | end 34 | 35 | it 'wraps array values into arrays of Resources' do 36 | unicorn = unicorn_class.new({ 'friends' => [{ 'gid' => '1' }] }, 37 | client: client) 38 | expect(unicorn.friends.first).to be_a(described_class) 39 | expect(unicorn.friends.first.gid).to eq('1') 40 | end 41 | 42 | describe '#refresh' do 43 | describe 'when the class responds to find_by_id' do 44 | it 'refetches itself' do 45 | unicorn = unicorn_class.new({ 'gid' => '5' }, client: client) 46 | expect(unicorn.refresh.gid).to eq('5') 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/asana/resources/tag_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'support/stub_api' 4 | require 'support/resources_helper' 5 | 6 | RSpec.describe Asana::Resources::Tag do 7 | let(:api) { StubAPI.new } 8 | let(:client) do 9 | Asana::Client.new do |c| 10 | c.authentication :access_token, 'foo' 11 | c.faraday_adapter api.to_proc 12 | end 13 | end 14 | 15 | include ResourcesHelper 16 | 17 | it 'contains backwards compatable method' do 18 | tag = described_class.new({ gid: 15 }, client: client) 19 | 20 | api.on(:get, "/tags/#{tag.gid}/tasks") do |response| 21 | response.body = { data: [{ foo: 'bar' }] } 22 | end 23 | 24 | res = tag.get_tasks_with_tag 25 | 26 | expect(res).not_to be_nil 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/asana/resources/task_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'support/stub_api' 4 | require 'support/resources_helper' 5 | 6 | RSpec.describe Asana::Resources::Task do 7 | let(:api) { StubAPI.new } 8 | let(:client) do 9 | Asana::Client.new do |c| 10 | c.authentication :access_token, 'foo' 11 | c.faraday_adapter api.to_proc 12 | end 13 | end 14 | 15 | include ResourcesHelper 16 | 17 | it 'contains backwards compatable method' do 18 | gid = '15' 19 | checks = 0 20 | 21 | api.on(:get, "/projects/#{gid}/tasks") do |response| 22 | response.body = { data: [] } 23 | checks += 1 24 | end 25 | 26 | client.tasks.find_by_project(project: gid) 27 | client.tasks.find_by_project(projectId: gid) 28 | client.tasks.find_by_project(project: nil, projectId: gid) 29 | client.tasks.find_by_project(project: gid, projectId: nil) 30 | 31 | expect(checks).to eq(4) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/asana/resources/webhooks_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'support/stub_api' 4 | require 'support/resources_helper' 5 | 6 | RSpec.describe Asana::Resources::Webhook do 7 | let(:api) { StubAPI.new } 8 | let(:webhook_data) do 9 | { 10 | gid: '222', 11 | resource: { 12 | gid: '111', 13 | name: 'the resource' 14 | }, 15 | target: 'https://foo/123', 16 | active: true 17 | } 18 | end 19 | let(:authentication) do 20 | Asana::Authentication::TokenAuthentication.new('token') 21 | end 22 | let(:client) do 23 | Asana::HttpClient.new(authentication: authentication, adapter: api.adapter) 24 | end 25 | 26 | include ResourcesHelper 27 | 28 | # rubocop:disable Metrics/AbcSize 29 | def verify_webhook_data(webhook) 30 | expect(webhook.gid).to eq(webhook_data[:gid]) 31 | expect(webhook.resource['gid']).to eq(webhook_data[:resource][:gid]) 32 | expect(webhook.resource['name']).to eq(webhook_data[:resource][:name]) 33 | expect(webhook.target).to eq(webhook_data[:target]) 34 | expect(webhook.active).to eq(webhook_data[:active]) 35 | end 36 | # rubocop:enable Metrics/AbcSize 37 | 38 | # rubocop:disable RSpec/NoExpectationExample 39 | it 'creates and deletes a webhook' do 40 | req = { 41 | data: { 42 | resource: '111', 43 | target: 'https://foo/123' 44 | } 45 | } 46 | 47 | api.on(:post, '/webhooks', req) do |response| 48 | response.body = { data: webhook_data } 49 | end 50 | api.on(:delete, '/webhooks/222') do |response| 51 | response.body = { data: {} } 52 | end 53 | 54 | webhook = described_class.create(client, 55 | resource: '111', 56 | target: 'https://foo/123') 57 | verify_webhook_data(webhook) 58 | 59 | webhook.delete_by_id 60 | end 61 | # rubocop:enable RSpec/NoExpectationExample 62 | 63 | it 'gets all webhooks' do 64 | api.on(:get, '/webhooks', workspace: '1337', per_page: 20) do |response| 65 | response.body = { data: [webhook_data] } 66 | end 67 | 68 | webhooks = described_class.get_all(client, workspace: '1337') 69 | verify_webhook_data(webhooks.first) 70 | expect(webhooks.length).to eq(1) 71 | end 72 | 73 | # rubocop:disable RSpec/NoExpectationExample 74 | it 'gets a webhook by gid' do 75 | api.on(:get, '/webhooks/222') do |response| 76 | response.body = { data: webhook_data } 77 | end 78 | 79 | webhook = described_class.get_by_id(client, '222') 80 | verify_webhook_data(webhook) 81 | end 82 | # rubocop:enable RSpec/NoExpectationExample 83 | end 84 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'simplecov' 4 | SimpleCov.start do 5 | add_filter '/spec/' 6 | add_filter '/lib/asana/resources/' 7 | add_filter '/lib/asana/resources/attachment.rb' 8 | add_filter '/lib/asana/resources/project.rb' 9 | add_filter '/lib/asana/resources/story.rb' 10 | add_filter '/lib/asana/resources/tag.rb' 11 | add_filter '/lib/asana/resources/task.rb' 12 | add_filter '/lib/asana/resources/team.rb' 13 | add_filter '/lib/asana/resources/user.rb' 14 | add_filter '/lib/asana/resources/workspace.rb' 15 | end 16 | # This file was generated by the `rspec --init` command. Conventionally, all 17 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 18 | # The generated `.rspec` file contains `--require spec_helper` which will cause 19 | # this file to always be loaded, without a need to explicitly require it in any 20 | # files. 21 | # 22 | # Given that it is always loaded, you are encouraged to keep this file as 23 | # light-weight as possible. Requiring heavyweight dependencies from this file 24 | # will add to the boot time of your test suite on EVERY test run, even for an 25 | # individual file that may not need all of that loaded. Instead, consider making 26 | # a separate helper file that requires the additional dependencies and performs 27 | # the additional setup, and require it from the spec files that actually need 28 | # it. 29 | # 30 | # The `.rspec` file also contains a few flags that are not defaults but that 31 | # users commonly want. 32 | # 33 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 34 | RSpec.configure do |config| 35 | # rspec-expectations config goes here. You can use an alternate 36 | # assertion/expectation library such as wrong or the stdlib/minitest 37 | # assertions if you prefer. 38 | config.expect_with :rspec do |expectations| 39 | # This option will default to `true` in RSpec 4. It makes the `description` 40 | # and `failure_message` of custom matchers include text for helper methods 41 | # defined using `chain`, e.g.: 42 | # be_bigger_than(2).and_smaller_than(4).description 43 | # # => "be bigger than 2 and smaller than 4" 44 | # ...rather than: 45 | # # => "be bigger than 2" 46 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 47 | end 48 | 49 | # rspec-mocks config goes here. You can use an alternate test double 50 | # library (such as bogus or mocha) by changing the `mock_with` option here. 51 | config.mock_with :rspec do |mocks| 52 | # Prevents you from mocking or stubbing a method that does not exist on 53 | # a real object. This is generally recommended, and will default to 54 | # `true` in RSpec 4. 55 | mocks.verify_partial_doubles = true 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /spec/support/codegen.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var path = require('path') 3 | var _ = require('lodash') 4 | var yaml = require('js-yaml'); 5 | var inflect = require('inflect'); 6 | 7 | function relativePath(p) { 8 | return path.join(__dirname, p) 9 | } 10 | 11 | var templateFile = fs.readFileSync(relativePath("../../lib/templates/resource.ejs"), 'utf8') 12 | 13 | var helpers = { 14 | plural: inflect.pluralize, 15 | single: inflect.singularize, 16 | camel: inflect.camelize, 17 | cap: inflect.capitalize, 18 | decap: inflect.decapitalize, 19 | snake: inflect.underscore, 20 | dash: inflect.dasherize, 21 | param: inflect.parameterize, 22 | human: inflect.humanize, 23 | resources: ["unicorn", "world"] 24 | } 25 | 26 | _.forEach(helpers.resources, function(name) { 27 | var yamlFile = fs.readFileSync(relativePath("../templates/" + name + ".yaml"), 'utf8') 28 | var resource = yaml.load(yamlFile) 29 | var output = _.template(templateFile, resource, {imports: helpers, variable: 'resource'}) 30 | 31 | fs.writeFile(relativePath("../templates/" + name + ".rb"), output, function(err) { 32 | if (err) return console.log(err) 33 | console.log(name + '.yaml > ' + name + '.rb') 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /spec/support/resources_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Internal: Helpeer to define resource classes in tests. 4 | module ResourcesHelper 5 | # Public: Defines a new resource. 6 | # 7 | # resource_name - [String] the unqualified, capitalized resource name, 8 | # e.g. 'Unicorn'. 9 | # body - [Proc] a block configuring the resource with its DSL. 10 | # 11 | # Returns the class object. 12 | def defresource(resource_name, &body) 13 | name = "Asana::Resources::#{resource_name}" 14 | Class.new(Asana::Resources::Resource) do 15 | define_singleton_method(:==) { |other| self.name == other.name } 16 | define_singleton_method(:name) { name } 17 | define_singleton_method(:to_s) { name } 18 | define_singleton_method(:inspect) { name } 19 | instance_eval(&body) if body 20 | end 21 | end 22 | 23 | def collection_of(elements) 24 | Asana::Resources::Collection.new(elements, client: nil) 25 | end 26 | 27 | def empty_collection 28 | collection_of([], client: nil) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/support/stub_api.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Internal: Represents a stub of the Asana API for testing purposes. Plays 4 | # nicely with Asana::HttpClient, being convertible to an adapter. 5 | # 6 | # Examples 7 | # 8 | # api = StubAPI.new 9 | # api.on(:get, "/users/me") { |response| 10 | # response.status = 200 # the default 11 | # response.body = { "data" => ... } 12 | # } 13 | # client = Asana::HttpClient.new(authentication: auth, adapter: api.adapter) 14 | # client.get("/users/me") 15 | # # => # ...}> 16 | # 17 | class StubAPI 18 | # Internal: Represents a stubbed response. 19 | class Response 20 | attr_accessor :env, :status, :headers, :body 21 | 22 | def initialize(env) 23 | @env = env 24 | end 25 | 26 | # Public: Returns a Rack-compliant version of the response. 27 | def to_rack 28 | [ 29 | status || 200, 30 | { 'Content-Type' => 'application/json' }.merge(headers || {}), 31 | JSON.dump(body) 32 | ] 33 | end 34 | end 35 | 36 | BASE_URI = Asana::HttpClient::BASE_URI 37 | private_constant :BASE_URI 38 | 39 | def initialize 40 | @stubs = Faraday::Adapter::Test::Stubs.new 41 | end 42 | 43 | # Public: Returns a function that takes a Faraday builder and configures it to 44 | # return stubbed responses. 45 | def to_proc 46 | ->(builder) { builder.adapter Faraday::Adapter::Test, @stubs } 47 | end 48 | alias adapter to_proc 49 | 50 | # Public: Adds a stub for a particular method and resource_uri. 51 | # 52 | # Yields a StubAPI::Response object so that the caller can set its body, and 53 | # optionally its status or headers. 54 | # 55 | # Examples 56 | # 57 | # api = StubAPI.new 58 | # api.on(:get, "/users/me") { |response| 59 | # response.status = 200 # the default 60 | # response.body = { "data" => ... } 61 | # } 62 | # 63 | # api.on(:put, "/users/me", { 'name' => 'John' }) { |response| 64 | # ... 65 | # } 66 | # 67 | def on(method, resource_uri, body = nil, &block) 68 | @stubs.send(method, *parse_args(method, resource_uri, body)) do |env| 69 | if body.is_a?(Proc) && !body.call(env.body) 70 | raise "Stubbed #{method.upcase} #{resource_uri} did not fulfill the " \ 71 | 'argument validation block' 72 | end 73 | Response.new(env).tap(&block).to_rack 74 | end 75 | end 76 | 77 | def parse_args(method, resource_uri, body) 78 | [BASE_URI + resource_uri].tap do |as| 79 | as.push JSON.dump(body) if %i[post put patch].include?(method) && !body.is_a?(Proc) 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /spec/templates/world.rb: -------------------------------------------------------------------------------- 1 | ### WARNING: This file is auto-generated by the asana-api-meta repo. Do not 2 | ### edit it manually. 3 | 4 | module Asana 5 | module Resources 6 | # A _world_ is a place where unicorns live. 7 | class World < Resource 8 | 9 | 10 | attr_reader :gid 11 | 12 | class << self 13 | # Returns the plural name of the resource. 14 | def plural_name 15 | 'worlds' 16 | end 17 | 18 | # Returns the complete world record for a single world. 19 | # 20 | # id - [Id] The world to get. 21 | # options - [Hash] the request I/O options. 22 | def find_by_id(client, id, options: {}) 23 | 24 | self.new(parse(client.get("/worlds/#{id}", options: options)).first, client: client) 25 | end 26 | end 27 | 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/templates/world.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: world 3 | comment: | 4 | A _world_ is a place where unicorns live. 5 | 6 | properties: 7 | 8 | - name: id 9 | type: Id 10 | 11 | - name: gid 12 | type: Gid 13 | 14 | actions: 15 | 16 | - name: findById 17 | method: GET 18 | path: "/worlds/%d" 19 | params: 20 | - name: world 21 | type: Gid 22 | required: true 23 | comment: The world to get. 24 | comment: | 25 | Returns the complete world record for a single world. 26 | -------------------------------------------------------------------------------- /swagger_templates/api.mustache: -------------------------------------------------------------------------------- 1 | ### WARNING: This file is auto-generated by our OpenAPI spec. Do not 2 | ### edit it manually. 3 | 4 | require_relative '../../resource_includes/response_helper' 5 | 6 | {{#operations}} 7 | module Asana 8 | module Resources 9 | class {{classname}}Base < Resource 10 | 11 | def self.inherited(base) 12 | Registry.register(base) 13 | end 14 | 15 | class << self 16 | {{#operation}}{{^formParams}} 17 | {{#contents}} 18 | # {{#summary}}{{{.}}}{{/summary}}{{^summary}}{{operationId}}{{/summary}} 19 | # 20 | {{#pathParams}} 21 | # {{paramName}} - [{{dataType}}] {{#required}} (required){{/required}}{{#optional}}(optional){{/optional}}{{#description}} {{{description}}}{{/description}}{{/pathParams}}{{#queryParams}}{{#neq baseName 'opt_pretty' 'opt_fields' 'offset' 'limit'}} 22 | # {{paramName}} - [{{~dataType~}}] {{#required}} (required){{/required}}{{#optional}}(optional){{/optional}}{{#description}} {{{description}}}{{/description}}{{/neq}}{{/queryParams}} 23 | # options - [Hash] the request I/O options{{#queryParams}}{{#eq baseName 'opt_pretty' 'opt_fields' 'offset' 'limit'}} 24 | # > {{paramName}} - [{{~dataType~}}] {{#required}} (required){{/required}}{{#optional}}(optional){{/optional}}{{#description}} {{{description}}}{{/description}}{{/eq}}{{/queryParams}} 25 | {{#eq httpMethod 'POST' 'PUT'}} 26 | # data - [Hash] the attributes to {{httpMethod}} 27 | {{/eq}} 28 | def {{operationId}}(client, {{#pathParams}}{{paramName}}: required("{{paramName}}"), {{/pathParams}}{{#queryParams}}{{#neq baseName 'opt_pretty' 'opt_fields' 'offset' 'limit'}}{{paramName}}: nil, {{/neq}}{{/queryParams}}options: {}{{#eq httpMethod 'POST' 'PUT'}}, **data{{/eq}}) 29 | path = "{{path}}"{{#pathParams}} 30 | path["{ {{~baseName~}} }"] = {{paramName}}{{/pathParams}}{{^moreThanCommon queryParams}} 31 | params = { {{#queryParams}}{{#neq baseName 'opt_pretty' 'opt_fields' 'offset' 'limit'}}{{#unless @first}}, {{/unless}}{{#neq paramName (fixSearchParams paramName)}}"{{/neq}}{{fixSearchParams paramName}}{{#neq paramName (fixSearchParams paramName)}}"{{/neq}}: {{paramName}}{{/neq}}{{/queryParams}} }.reject { |_,v| v.nil? || Array(v).empty? }{{/moreThanCommon}} 32 | {{#returnContainer}}Collection.new({{/returnContainer}}{{^returnContainer}}{{#firstClassResponseObject returnType}}{{firstClassResponseObject returnType}}.new({{/firstClassResponseObject}}{{/returnContainer}}parse(client.{{toLowerCase httpMethod}}(path, {{^moreThanCommon queryParams}}params: params, {{/moreThanCommon}}{{#eq httpMethod 'POST' 'PUT'}}body: data, {{/eq}}{{#queryParams.length}}params: params, {{/queryParams.length}}options: options)){{^returnContainer}}.first{{/returnContainer}}{{#returnContainer}}, type: {{#if (firstClassResponseObject returnType)}}{{firstClassResponseObject returnType}}{{else}}Resource{{/if}}{{/returnContainer}}{{#returnContainer}}, client: client){{/returnContainer}}{{^returnContainer}}{{#firstClassResponseObject returnType}}, client: client){{/firstClassResponseObject}}{{/returnContainer}} 33 | end 34 | 35 | {{/contents}} 36 | {{/formParams}}{{/operation}} 37 | end 38 | end 39 | end 40 | end 41 | {{/operations}} 42 | -------------------------------------------------------------------------------- /swagger_templates/api_doc.mustache: -------------------------------------------------------------------------------- 1 | {{#operations}}{{toLowerCase classname}}: {{#operation}}{{#contents}} 2 | {{operationId}}: >- 3 | require 'asana' 4 | 5 | 6 | client = Asana::Client.new do |c| 7 | c.authentication :access_token, 'PERSONAL_ACCESS_TOKEN' 8 | end 9 | 10 | 11 | result = client.{{toLowerCase baseName}}.{{operationId}}({{#pathParams}}{{paramName}}: '{{paramName}}', {{/pathParams}}{{#queryParams}}{{#eq required true}}{{paramName}}: '{{example}}', {{/eq}}{{/queryParams}}{{#eq httpMethod 'GET'}}param: "value", param: "value", {{/eq}}{{#eq httpMethod 'POST' 'PUT'}}field: "value", field: "value", {{/eq}}options: {pretty: true}){{/contents}}{{/operation}} 12 | {{/operations}} 13 | -------------------------------------------------------------------------------- /swagger_templates/ruby-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageName" : "asana", 3 | "templateDir": "swagger_templates", 4 | "hideGenerationTimestamp": true, 5 | "apiTests": false 6 | } 7 | --------------------------------------------------------------------------------