class CanvasApi::Builder

Public Class Methods

build(project_root, client_app_path, server_app_path, elixir_app_path, rb_graphql_app_path) click to toggle source

project_root: This is the directory where the canvas_urls.rb file will be written. This file contains all urls and functions for access to the Canvas API from this gem (lms_api). client_app_path: This where all client side Javascript for accessing the Canvas API will be written. server_app_path: This is where all server side Javascript for accessing the Canvas API will be written. Currently, this is generating GraphQL for Javascript and Ruby

# File lib/canvas_api/builder.rb, line 16
def self.build(project_root, client_app_path, server_app_path, elixir_app_path, rb_graphql_app_path)
  endpoint = "https://canvas.instructure.com/doc/api"
  directory = HTTParty.get("#{endpoint}/api-docs.json")

  lms_urls_rb = []
  lms_urls_js = []
  lms_urls_ex = []
  course_ids_required_rb = []
  models = []
  js_graphql_queries = []
  js_graphql_mutations = []

  rb_graphql_fields = []
  rb_graphql_mutations = []
  rb_forward_declarations = []

  nicknames = []

  # Elixir has a default action that raises
  lms_urls_ex << CanvasApi::Render.new("./templates/ex_default_action.erb", nil, nil, nil, nil, nil, nil, nil).render

  directory["apis"].each do |api|
    puts "Generating #{api['description']}"
    resource = HTTParty.get("#{endpoint}#{api['path']}")

    # HACK The canvas docs are missing one of the allowable value in modules
    # TODO we might want to create a hacks class at some point to contain all these
    if api['path'] == "/modules.json"
      if !resource["models"]["CompletionRequirement"]["properties"]["type"]["allowableValues"]["values"].include?("must_mark_done")
        resource["models"]["CompletionRequirement"]["properties"]["type"]["allowableValues"]["values"].push("must_mark_done")
      end
    end

    constants = []
    resource["apis"]&.each do |resource_api|
      resource_api["operations"].each do |operation|

        # Prevent duplicates
        nickname = operation["nickname"]
        if nicknames.include?(nickname)
          nickname = "#{api["description"].gsub(" ", "_").downcase}_#{nickname}"
        end
        nicknames << nickname
        operation["nickname"] = nickname

        parameters = filter_duplicate_parameters(operation["parameters"])
        constants << CanvasApi::Render.new("./templates/constant.erb", api, resource, resource_api, operation, parameters, nil, nil).render
        lms_urls_rb << CanvasApi::Render.new("./templates/rb_url.erb", api, resource, resource_api, operation, parameters, nil, nil).render
        lms_urls_js << CanvasApi::Render.new("./templates/js_url.erb", api, resource, resource_api, operation, parameters, nil, nil).render
        lms_urls_ex << CanvasApi::Render.new("./templates/ex_url.erb", api, resource, resource_api, operation, parameters, nil, nil).render
        lms_urls_ex << CanvasApi::Render.new("./templates/ex_action.erb", api, resource, resource_api, operation, parameters, nil, nil).render

        if parameters.detect{ |param| param["name"] == "course_id" && param["paramType"] == "path" }
          course_ids_required_rb << CanvasApi::Render.new("./templates/course_id_required.erb", api, resource, resource_api, operation, parameters, nil, nil).render
        end

        if operation["method"].casecmp("GET") == 0
          js_graphql_queries << CanvasApi::Render.new("./templates/js_graphql_query.erb", api, resource, resource_api, operation, parameters, nil, nil).render

          # One file per Canvas graphql resolver
          canvas_graphql_resolver_renderer = CanvasApi::Render.new("./templates/rb_graphql_resolver.erb", api, resource, resource_api, operation, parameters, nil, nil)
          canvas_graphql_resolver_renderer.save("#{rb_graphql_app_path}/lib/lms_graphql/resolvers/canvas/#{canvas_graphql_resolver_renderer.nickname}.rb")
          rb_graphql_fields << CanvasApi::Render.new("./templates/rb_graphql_field.erb", api, resource, resource_api, operation, parameters, nil, nil).render
        else
          js_graphql_mutations << CanvasApi::Render.new("./templates/js_graphql_mutation.erb", api, resource, resource_api, operation, parameters, nil, nil).render

          rb_graphql_mutation_renderer = CanvasApi::Render.new("./templates/rb_graphql_mutation.erb", api, resource, resource_api, operation, parameters, nil, nil)
          rb_graphql_mutation_renderer.save("#{rb_graphql_app_path}/lib/lms_graphql/mutations/canvas/#{rb_graphql_mutation_renderer.nickname}.rb")
          rb_graphql_mutations << CanvasApi::Render.new("./templates/rb_graphql_mutation_include.erb", api, resource, resource_api, operation, parameters, nil, nil).render
        end

      end
    end

    resource["models"].map do |_name, model|
      if model["properties"] # Don't generate models without properties
        models << CanvasApi::Render.new("./templates/js_graphql_model.erb", api, resource, nil, nil, nil, nil, model).render
      end

      # Generate one file for each Canvas graphql type
      canvas_graphql_type_render = CanvasApi::Render.new("./templates/rb_graphql_type.erb", api, resource, nil, nil, nil, nil, model)
      canvas_graphql_type_render.save("#{rb_graphql_app_path}/lib/lms_graphql/types/canvas/#{model['id'].underscore.singularize}.rb")

      canvas_graphql_input_render = CanvasApi::Render.new("./templates/rb_graphql_input_type.erb", api, resource, nil, nil, nil, nil, model)
      canvas_graphql_input_render.save("#{rb_graphql_app_path}/lib/lms_graphql/types/canvas/#{model['id'].underscore.singularize}_input.rb")

      rb_forward_declarations << "class Canvas#{model['id'].singularize}Input < BaseInputObject;end"
      rb_forward_declarations << "class Canvas#{model['id'].singularize} < BaseType;end"
    end

    # Generate one file of constants for every LMS API
    constants_renderer = CanvasApi::Render.new("./templates/constants.erb", api, resource, nil, nil, nil, constants, nil)
    constants_renderer.save("#{client_app_path}/#{constants_renderer.name}.js")
  end

  CanvasApi::Render.new("./templates/rb_urls.erb", nil, nil, nil, nil, nil, lms_urls_rb, nil).save("#{project_root}/lib/lms/canvas_urls.rb")
  CanvasApi::Render.new("./templates/js_urls.erb", nil, nil, nil, nil, nil, lms_urls_js, nil).save("#{server_app_path}/lib/canvas/urls.js")

  # The elixir urls are sorted, to prevent linter errors
  CanvasApi::Render.new("./templates/ex_urls.erb", nil, nil, nil, nil, nil, lms_urls_ex.sort, nil).save("#{elixir_app_path}/lib/canvas/actions.ex")

  CanvasApi::Render.new("./templates/course_ids_required.erb", nil, nil, nil, nil, nil, course_ids_required_rb, nil).save("#{project_root}/lib/lms/course_ids_required.rb")

  # GraphQL Javascript - still not complete
  CanvasApi::Render.new("./templates/js_graphql_types.erb", nil, nil, nil, nil, nil, models.compact, nil).save("#{server_app_path}/lib/canvas/graphql_types.js")
  CanvasApi::Render.new("./templates/js_graphql_queries.erb", nil, nil, nil, nil, nil, js_graphql_queries, nil).save("#{server_app_path}/lib/canvas/graphql_queries.js")
  CanvasApi::Render.new("./templates/js_graphql_mutations.erb", nil, nil, nil, nil, nil, js_graphql_mutations, nil).save("#{server_app_path}/lib/canvas/graphql_mutations.js")

  # GraphQL Ruby
  CanvasApi::Render.new("./templates/rb_forward_declarations.erb", nil, nil, nil, nil, nil, rb_forward_declarations, nil).save("#{rb_graphql_app_path}/lib/lms_graphql/types/canvas_forward_declarations.rb")
  CanvasApi::Render.new("./templates/rb_graphql_root_query.erb", nil, nil, nil, nil, nil, rb_graphql_fields, nil).save("#{rb_graphql_app_path}/lib/lms_graphql/types/canvas/query_type.rb")
  CanvasApi::Render.new("./templates/rb_graphql_mutations.erb", nil, nil, nil, nil, nil, rb_graphql_mutations, nil).save("#{rb_graphql_app_path}/lib/lms_graphql/mutations/canvas/mutations.rb")
end
filter_duplicate_parameters(parameters) click to toggle source
# File lib/canvas_api/builder.rb, line 130
def self.filter_duplicate_parameters(parameters)
  # Canvas placed a deprecation warning
  # at the bottom of their documentation
  # for editing an assignment. This caused
  # us to create two instances of that specific field
  # this will filter that out (and ony other instances where they might do that)
  names = Set.new
  parameters.select do |param|
    included = names.include? param["name"]
    names.add(param["name"]) if !included
    !included
  end
end