diff --git a/Gemfile b/Gemfile index bc01f3417337e9bb370674d09fa94104f30ec9d6..9e3b7745fbd158565a8d16a708ed39b48459473c 100644 --- a/Gemfile +++ b/Gemfile @@ -162,6 +162,8 @@ gem 'grape-swagger', '~> 2.1.2', group: [:development, :test], feature_category: gem 'grape-swagger-entity', '~> 0.5.5', group: [:development, :test], feature_category: :api gem 'grape-path-helpers', '~> 2.0.1', feature_category: :api gem 'rack-cors', '~> 2.0.1', require: 'rack/cors', feature_category: :shared +gem 'rswag-api', feature_category: :api +gem 'rswag-ui', feature_category: :api # GraphQL API gem 'graphql', '2.5.11', feature_category: :api @@ -516,6 +518,7 @@ group :development, :test do gem 'database_cleaner-active_record', '~> 2.2.0', feature_category: :database gem 'rspec-rails', '~> 7.1.0', feature_category: :shared + gem 'rswag-specs', feature_category: :api gem 'factory_bot_rails', '~> 6.5.0', feature_category: :tooling # Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826) diff --git a/Gemfile.lock b/Gemfile.lock index a0d3e8721446f558331207bb4e0fadb8f3c8f85d..5f9a9764cfa165daa1989af3de3d0bc12dc8b970 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1713,6 +1713,17 @@ GEM activerecord get_process_mem rails + rswag-api (2.16.0) + activesupport (>= 5.2, < 8.1) + railties (>= 5.2, < 8.1) + rswag-specs (2.16.0) + activesupport (>= 5.2, < 8.1) + json-schema (>= 2.2, < 6.0) + railties (>= 5.2, < 8.1) + rspec-core (>= 2.14) + rswag-ui (2.16.0) + actionpack (>= 5.2, < 8.1) + railties (>= 5.2, < 8.1) rubocop (1.71.1) json (~> 2.3) language_server-protocol (>= 3.17.0) @@ -2370,6 +2381,9 @@ DEPENDENCIES rspec-retry (~> 0.6.2) rspec_junit_formatter rspec_profiling (~> 0.0.9) + rswag-api + rswag-specs + rswag-ui rubocop ruby-lsp (~> 0.23.0) ruby-lsp-rails (~> 0.3.6) diff --git a/config/initializers/rswag_api.rb b/config/initializers/rswag_api.rb new file mode 100644 index 0000000000000000000000000000000000000000..ff064e3262a2052a86032023a3cc316b26bfeaea --- /dev/null +++ b/config/initializers/rswag_api.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# rubocop:disable all + +Rswag::Api.configure do |c| + # Specify a root folder where Swagger JSON files are located + # This is used by the Swagger middleware to serve requests for API descriptions + # NOTE: If you're using rswag-specs to generate Swagger, you'll need to ensure + # that it's configured to generate files in the same folder + c.openapi_root = Rails.root.to_s + '/swagger' + + # Inject a lambda function to alter the returned Swagger prior to serialization + # The function will have access to the rack env for the current request + # For example, you could leverage this to dynamically assign the "host" property + # + # c.swagger_filter = lambda { |swagger, env| swagger['host'] = env['HTTP_HOST'] } +end diff --git a/config/initializers/rswag_ui.rb b/config/initializers/rswag_ui.rb new file mode 100644 index 0000000000000000000000000000000000000000..182bcc00f7548c14f3abd2ce2ef72377491c68aa --- /dev/null +++ b/config/initializers/rswag_ui.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +Rswag::Ui.configure do |c| + # List the Swagger endpoints that you want to be documented through the + # swagger-ui. The first parameter is the path (absolute or relative to the UI + # host) to the corresponding endpoint and the second is a title that will be + # displayed in the document selector. + # NOTE: If you're using rspec-api to expose Swagger files + # (under openapi_root) as JSON or YAML endpoints, then the list below should + # correspond to the relative paths for those endpoints. + + c.swagger_endpoint '/api-docs/v1/swagger.yaml', 'API V1 Docs' + + # Add Basic Auth in case your API is private + # c.basic_auth_enabled = true + # c.basic_auth_credentials 'username', 'password' +end diff --git a/config/routes.rb b/config/routes.rb index 151c7fa1c1b45791d964abbbb82e78d2f05535a5..2de38332132032e4bb50cc4c080cbf845a64b580 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -5,6 +5,8 @@ InitializerConnections.raise_if_new_database_connection do Rails.application.routes.draw do + mount Rswag::Ui::Engine => '/api-docs' + mount Rswag::Api::Engine => '/api-docs' concern :access_requestable do get :request_access, on: :collection post :request_access, on: :collection diff --git a/lib/grape_to_rswag.rb b/lib/grape_to_rswag.rb new file mode 100644 index 0000000000000000000000000000000000000000..b4d72e9a9bb80d9c174a97cc7191fae83f2ebef4 --- /dev/null +++ b/lib/grape_to_rswag.rb @@ -0,0 +1,507 @@ +# frozen_string_literal: true + +# rubocop:disable all + +class GrapeToRswag + attr_reader :api_class, :output_dir + + def initialize(api_class, output_dir: 'spec/requests/rswag') + @api_class = api_class + @output_dir = output_dir + end + + def convert_to_spec + # Any methods not defined in this class are `Grape::DSL#methods` + # acting on the existing grape entitites + routes = api_class.routes + + #the main funtionality + content = build_spec_file(routes) + + # Build the file name from the class name + resource_name = api_class.name.demodulize.underscore + + # Write the spec to file and return the file path + file_path = write_file(resource_name, content) + + puts "Spec written to: #{file_path}" + + # prints out the content in the console for visual ref + { content: content, file_path: file_path } + end + + private + + # *** BUILDER METHODS -- building spec sections *** + + def build_spec_file(routes) + # base structure for the spec, we will need to add feature_category + # May want to format class name a bit better + # rubocop disabled only for development + # swagger helper may need to be configured more, autogenerated with gem + + <<~RUBY + # frozen_string_literal: true + # rubocop:disable all -- for development + + require 'swagger_helper' + + RSpec.describe "#{api_class.name.demodulize} API", type: :request do + #{build_paths(routes)} + end + RUBY + end + + def build_paths(routes) + # there can be multiple routes under the each path + # this method organizes routes within paths + # the extra lines in the join is just for readability + + paths = routes.group_by(&:path) + + paths.map do |path, routes| + build_path_block(path, routes) + end.join("\n") + end + + def build_path_block(path, routes) + # we need to format_path because + # - url params need to be wrapped in curly braces {} + # - url param `:version` in path even though we only use v4 + + <<~RUBY + + path "#{format_path(path)}" do + #{routes.map { |route| build_route_test(route) }.join("\n")} + end + RUBY + end + + def build_route_test(route) + # builds individual route tests + route_attrs = route.attributes + method = route_attrs.request_method.downcase + # formats descriptions or adds default one + description = route.description&.gsub("'", "\\'") || "#{method.upcase} #{route.path}" + # for instances where there are no tags adds one for the class name at the least + tags = route_attrs.tags || [ api_class.name.demodulize.underscore.to_s ] + params = format_params(route) + + operation = [] + operation << " #{method} '#{description}' do" + operation << " tags #{tags}" + operation << " produces 'application/json'" + + operation << " consumes 'application/json'" if %w[post put patch].include?(method) + + # Add parameters + params.each do |param| + operation << format_parameter_line(param) + end + + # Add request body for POST/PUT/PATCH + if %w[post put patch].include?(method) && route.params + body_params = route.params.reject { |k, _| params.any? { |p| p[:name] == k.to_s } } + operation << build_request_body(body_params) unless body_params.empty? + end + + # These are the the test blocks based on possible responses + operation << "" + operation << build_responses(route) + operation << " end" + # each array element represents a test line + operation.join("\n") + end + + def build_request_body(params) + # requests that create or update resources need a request body + + properties = params.map do |name, details| + type_info = grape_type_to_openapi(details[:type]) + + if type_info[:oneOf] + # Handle oneOf case + one_of_schemas = type_info[:oneOf].map(&:inspect).join(', ') + " #{name}: { oneOf: [#{one_of_schemas}] }" + else + # Handle simple type case + " #{name}: { type: #{type_info[:type].inspect} }" + end + end.join(",\n") + + required = params.select { |_, v| v[:required] }.keys + req_str = required.empty? ? "[]" : "[#{required.map { |r| ":#{r}" }.join(', ')}]" + + <<~RUBY.chomp + + parameter name: :body, in: :body, schema: { + type: :object, + properties: { + #{properties} + }, + required: #{req_str} + } + RUBY + end + + # *** FORMATTING METHODS -- format grape dsl formatting to rswag formatting *** + + # referenced above, conform to rswag dsl + def format_path(path) + path.gsub(':version', 'v4') # Replace :version with v4 + .gsub(/\(\.\:format\)/, '') # Remove (.:format) + .gsub(/:(\w+)/, '{\1}') # Replace :param with {param} + end + + def format_params(route) + params = [] + # just in case return (this may not be needed) + return params unless route.params + + request_method = route.attributes.request_method.downcase + + # Takes care of path parameters + path_params = route.path.scan(/:(\w+)/).flatten - %w[version format] + path_params.each do |param_name| + param_hash = { + name: param_name, + in: :path, + required: true + } + + # param details include type, documentation, requirement boolean, desc, enums, defaults + details = route.params[param_name] || route.params[param_name.to_sym] + + # Check if this path param has details in route.params (check both string and symbol keys) + unless details.blank? + + # Get type from details + type_info = grape_type_to_openapi(details[:type]) + param_hash.merge!(type_info) + + # Add description if available + param_hash[:description] = details[:desc] if details[:desc] + + # Add example if available + if details[:documentation] && details[:documentation][:example] + param_hash[:example] = details[:documentation][:example] + end + + # Add enum values if present + param_hash[:enum] = details[:values] if details[:values] + else + # Default type if no details found + param_hash[:type] = param_name == 'id' ? :integer : :string + + # Default description for common params (only if no details were found) + param_hash[:description] = 'The ID of the resource' if param_name == 'id' + end + + params << param_hash + end + + # Only add query params for GET requests + # For POST/PUT/PATCH, non-path params should go in request body instead + if request_method == 'get' + # select and format query params ==> in: :query + query_params = route.params.reject { |name, _| path_params.include?(name.to_s) } + query_params.each do |name, details| + + param_hash = { + name: name.to_s, + in: :query + } + + # Merge the type information properly + type_info = grape_type_to_openapi(details[:type]) + param_hash.merge!(type_info) + + # Add description from Grape params + param_hash[:description] = details[:desc] if details[:desc] + + # Add default value if present + # There is an unhandled issue here where we do have some dynamic defaults defined + # as procs ie in environment there is a default: -> { 30.days.ago } + # for this POC choosing to ignore this edge case + param_hash[:default] = details[:default] if details[:default] && !details[:default].is_a?(Proc) + + # Add enum values if present + param_hash[:enum] = details[:values] if details[:values] + + # Add example from documentation if present + if details[:documentation] && details[:documentation][:example] + param_hash[:example] = details[:documentation][:example] + end + + # Add required field + param_hash[:required] = details[:required] || false + + params << param_hash + end + end + + params + end + + def format_parameter_line(param) + # Build parameter line with proper formatting + # I have attempted to cover all the different param options + # it is possible there are some I have missed + # calls param_value#inspect to ensure proper syntax, escape single quotes and apostrophes etc... + + param_options = [] + param_options << "name: #{param[:name].inspect}" + param_options << "in: #{param[:in].inspect}" + + # Add description if present + param_options << "description: #{param[:description].inspect}" if param[:description] + + # Format the required field + param_options << "required: #{param[:required]}" if param.key?(:required) + + # Build schema hash for OpenAPI 3.0 compliance + schema_parts = [] + + # Handle oneOf vs simple types + if param[:oneOf] + one_of_schemas = param[:oneOf].map(&:inspect).join(', ') + schema_parts << "oneOf: [#{one_of_schemas}]" + else + schema_parts << "type: #{param[:type].inspect}" if param[:type] + end + + schema_parts << "format: #{param[:format].inspect}" if param[:format] + + # Handle array items if present + # grape uses type definitions of Array[Integer] or Array[String] which need a type for the items in the array + if param[:items] + items_formatted = param[:items].map { |k, v| "#{k}: #{v.inspect}" }.join(', ') + schema_parts << "items: { #{items_formatted} }" + end + + # Add enum values if present (must be inside schema for OpenAPI 3.0) + schema_parts << "enum: #{param[:enum].inspect}" if param[:enum] + + # Add example if present (must be inside schema for OpenAPI 3.0) + schema_parts << "example: #{param[:example].inspect}" if param.key?(:example) + + # Add default value if present (must be inside schema for OpenAPI 3.0) + schema_parts << "default: #{param[:default].inspect}" if param.key?(:default) + + # Wrap all type/format/enum/example/default info in schema object + param_options << "schema: { #{schema_parts.join(', ')} }" + + " parameter #{param_options.join(', ')}" + end + + def grape_type_to_openapi(type) + return { type: :string } unless type + + # This may need to be expanded and likely defined in a constant + # likely we would extract param formatting into it's own class as well + type_mapping = { + 'String' => { type: :string }, + 'Integer' => { type: :integer }, + 'Float' => { type: :number }, + 'Boolean' => { type: :boolean }, + 'Date' => { type: :string, format: :date }, + 'DateTime' => { type: :string, format: 'date-time' }, + 'Time' => { type: :string, format: 'date-time' }, + 'Array' => { type: :array }, + 'Array[String]' => { type: :array, items: { type: :string } }, + 'Array[Integer]' => { type: :array, items: { type: :integer } }, + 'Hash' => { type: :object } + } + + # we need to handle the oneOf for param types + # there are many examples of the url param or an id param being passed an array of types + if type.match(/^\[(.+)\]$/) + # when an array passed to type is looks something like "[String, Integer]" + types = type.tr("[]", "").split(",").map(&:strip) + { oneOf: types.map { |t| type_mapping[t] || { type: :string } } } + else + type_mapping[type.to_s] || { type: :string } + end + end + + # *** RESPONSE BUILDERS - builds response bodies *** + + + def build_responses(route) + responses = [] + + # Build success response (200) + responses << build_success_response(route) + + # Build failure responses from grapeMethod#http_codes(defined in API class definition as failure) + if route.http_codes && !route.http_codes.empty? + route.http_codes.each do |http_code| + responses << build_failure_response(http_code[:code], http_code[:message]) + end + else + # Default failure response if no http_codes specified + responses << build_failure_response(404, 'not found') + end + + responses.join("\n") + end + + def build_success_response(route) + # Check if there's an entity first + if route.respond_to?(:entity) && route.entity + # Determine if response should be an array (this is an existing grape attr) + is_array = route.attributes.is_array + + response_lines = [] + response_lines << " response '200', 'successful' do" + + # Build schema with entity properties + schema_content = build_response_schema(route, is_array) + response_lines << schema_content + + response_lines << " run_test!" + response_lines << " end" + + response_lines.join("\n") + else + # No entity - return 204 No Content + # I noticed that several of our destroy routes return response objects/entities + # if we do not want this and we want to always return 204 even if we previously + # defined a response, we could just code all delete actions to return 204 + response_lines = [] + response_lines << " response '204', 'no content' do" + response_lines << " run_test!" + response_lines << " end" + + response_lines.join("\n") + end + end + + def build_response_schema(route, is_array) + # Check if route has an entity + if route.respond_to?(:entity) && route.entity + properties = extract_entity_properties(route.entity) + + if is_array + build_array_schema_with_properties(properties) + else + build_object_schema_with_properties(properties) + end + else + # No entity - check if this is a destroy action or similar + method = route.attributes.request_method.downcase + if method == 'delete' || route.description&.match?(/destroy|delete/i) + # Destroy actions typically return empty objects or 204 status + " schema type: :object" + elsif is_array + # Default array response without properties + " schema type: :array, items: { type: :object }" + else + # Default object response without properties + " schema type: :object" + end + end + end + + # Translates grape entities for responses + # builds/translates grapeEntity --> rswag response object for use with response schema + def extract_entity_properties(entity_class) + properties = {} + + # Get documentation directly from the entity + entity_class.documentation.each do |field_name, field_info| + property_name = field_name.to_s + + # Convert grape type to OpenAPI type using same method used for formatting params + type_info = grape_type_to_openapi(field_info[:type] || field_info['type']) + + property_info = type_info.dup + + # Add example if available + example = field_info[:example] || field_info['example'] + property_info[:example] = example if example + + # Add description if available + desc = field_info[:desc] || field_info['desc'] || field_info[:description] || field_info['description'] + property_info[:description] = desc if desc + + properties[property_name] = property_info + end + + properties + end + + def build_object_schema_with_properties(properties) + if properties.empty? + return " schema type: :object" + end + + schema_lines = [] + schema_lines << " schema type: :object, properties: {" + + properties.each_with_index do |(name, info), index| + if info[:oneOf] + one_of_schemas = info[:oneOf].map(&:inspect).join(', ') + line = " #{name}: { oneOf: [#{one_of_schemas}] }" + else + line = " #{name}: { type: :#{info[:type]}" + line += ", description: '#{info[:description].gsub("'", "\\'")}'" if info[:description] + line += ", example: #{info[:example].inspect}" if info[:example] + line += " }" + end + line += "," unless index == properties.length - 1 + schema_lines << line + end + + schema_lines << " }" + schema_lines.join("\n") + end + + def build_array_schema_with_properties(properties) + if properties.empty? + return " schema type: :array, items: { type: :object }" + end + + schema_lines = [] + schema_lines << " schema type: :array, items: {" + schema_lines << " type: :object," + schema_lines << " properties: {" + + properties.each_with_index do |(name, info), index| + if info[:oneOf] + one_of_schemas = info[:oneOf].map(&:inspect).join(', ') + line = " #{name}: { oneOf: [#{one_of_schemas}] }" + else + line = " #{name}: { type: :#{info[:type]}" + line += ", description: '#{info[:description].gsub("'", "\\'")}'" if info[:description] + line += ", example: #{info[:example].inspect}" if info[:example] + line += " }" + end + line += "," unless index == properties.length - 1 + schema_lines << line + end + + schema_lines << " }" + schema_lines << " }" + schema_lines.join("\n") + end + + + + def build_failure_response(code, message) + response_lines = [] + response_lines << " response '#{code}', '#{message.downcase}' do" + response_lines << " run_test!" + response_lines << " end" + + response_lines.join("\n") + end + +# *** FILE WRITER + def write_file(resource, content) + FileUtils.mkdir_p(@output_dir) + file_path = File.join(@output_dir, "#{resource}_spec.rb") + File.write(file_path, content) + file_path + end +end diff --git a/spec/requests/rswag/audit_events_spec.rb b/spec/requests/rswag/audit_events_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..c039cac194cb3edfe46202681b0c0fa821c23684 --- /dev/null +++ b/spec/requests/rswag/audit_events_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true +# rubocop:disable all -- for development + +require 'swagger_helper' + +RSpec.describe "AuditEvents API", type: :request do + + path "/api/v4/audit_events" do + get 'Get the list of audit events' do + tags ["audit_events"] + produces 'application/json' + parameter name: "entity_type", in: :query, description: "Return list of audit events for the specified entity type", required: true, schema: { type: :string, enum: ["Project", "User", "Group", "Gitlab::Audit::InstanceScope"] } + parameter name: "entity_id", in: :query, required: false, schema: { type: :integer } + parameter name: "created_after", in: :query, description: "Return audit events created after the specified time", required: false, schema: { type: :string, format: "date-time", example: "2016-01-19T09:05:50.355Z" } + parameter name: "created_before", in: :query, description: "Return audit events created before the specified time", required: false, schema: { type: :string, format: "date-time", example: "2016-01-19T09:05:50.355Z" } + parameter name: "page", in: :query, description: "Current page number", required: false, schema: { type: :integer, example: 1, default: 1 } + parameter name: "per_page", in: :query, description: "Number of items per page", required: false, schema: { type: :integer, example: 20, default: 20 } + + response '200', 'successful' do + schema type: :array, items: { type: :object } + run_test! + end + response '404', 'not found' do + run_test! + end + end + end + + + path "/api/v4/audit_events/{id}" do + get 'Get single audit event' do + tags ["audit_events"] + produces 'application/json' + parameter name: "id", in: :path, description: "The ID of audit event", required: true, schema: { type: :integer } + + response '200', 'successful' do + schema type: :object + run_test! + end + response '404', 'not found' do + run_test! + end + end + end + +end diff --git a/spec/requests/rswag/environments_spec.rb b/spec/requests/rswag/environments_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..ee22d505431dcc7590c1871abf1192cfdd9dd297 --- /dev/null +++ b/spec/requests/rswag/environments_spec.rb @@ -0,0 +1,311 @@ +# frozen_string_literal: true +# rubocop:disable all -- for development + +require 'swagger_helper' + +RSpec.describe "Environments API", type: :request do + + path "/api/v4/projects/{id}/environments" do + get 'List environments' do + tags ["environments"] + produces 'application/json' + parameter name: "id", in: :path, description: "The ID or URL-encoded path of the project owned by the authenticated user", required: true, schema: { oneOf: [{:type=>:string}, {:type=>:integer}] } + parameter name: "page", in: :query, description: "Current page number", required: false, schema: { type: :integer, example: 1, default: 1 } + parameter name: "per_page", in: :query, description: "Number of items per page", required: false, schema: { type: :integer, example: 20, default: 20 } + parameter name: "name", in: :query, description: "Return the environment with this name. Mutually exclusive with search", required: false, schema: { type: :string } + parameter name: "search", in: :query, description: "Return list of environments matching the search criteria. Mutually exclusive with name. Must be at least 3 characters.", required: false, schema: { type: :string } + parameter name: "states", in: :query, description: "List all environments that match a specific state. Accepted values: `available`, `stopping`, or `stopped`. If no state value given, returns all environments", required: false, schema: { type: :string, enum: ["stopped", "stopping", "available"] } + + response '200', 'successful' do + schema type: :array, items: { + type: :object, + properties: { + id: { type: :string, example: 1 }, + name: { type: :string, example: "deploy" }, + slug: { type: :string, example: "deploy" }, + external_url: { type: :string, example: "https://deploy.gitlab.example.com" }, + created_at: { type: :string, example: "2019-05-25T18:55:13.252Z" }, + updated_at: { type: :string, example: "2019-05-25T18:55:13.252Z" }, + tier: { type: :string, example: "development" }, + state: { type: :string, example: "available" }, + auto_stop_at: { type: :string, example: "2019-05-25T18:55:13.252Z" }, + description: { type: :string, example: "description" }, + auto_stop_setting: { type: :string, example: "always" } + } + } + run_test! + end + response '401', 'unauthorized' do + run_test! + end + response '404', 'not found' do + run_test! + end + end + post 'Create a new environment' do + tags ["environments"] + produces 'application/json' + consumes 'application/json' + parameter name: "id", in: :path, description: "The ID or URL-encoded path of the project owned by the authenticated user", required: true, schema: { oneOf: [{:type=>:string}, {:type=>:integer}] } + + parameter name: :body, in: :body, schema: { + type: :object, + properties: { + name: { type: :string }, + external_url: { type: :string }, + slug: { type: :string }, + tier: { type: :string }, + cluster_agent_id: { type: :integer }, + kubernetes_namespace: { type: :string }, + flux_resource_path: { type: :string }, + description: { type: :string }, + auto_stop_setting: { type: :string } + }, + required: [:name] + } + + response '200', 'successful' do + schema type: :object, properties: { + id: { type: :string, example: 1 }, + name: { type: :string, example: "deploy" }, + slug: { type: :string, example: "deploy" }, + external_url: { type: :string, example: "https://deploy.gitlab.example.com" }, + created_at: { type: :string, example: "2019-05-25T18:55:13.252Z" }, + updated_at: { type: :string, example: "2019-05-25T18:55:13.252Z" }, + tier: { type: :string, example: "development" }, + state: { type: :string, example: "available" }, + auto_stop_at: { type: :string, example: "2019-05-25T18:55:13.252Z" }, + description: { type: :string, example: "description" }, + auto_stop_setting: { type: :string, example: "always" } + } + run_test! + end + response '400', 'bad request' do + run_test! + end + response '401', 'unauthorized' do + run_test! + end + response '404', 'not found' do + run_test! + end + end + end + + + path "/api/v4/projects/{id}/environments/{environment_id}" do + put 'Update an existing environment' do + tags ["environments"] + produces 'application/json' + consumes 'application/json' + parameter name: "id", in: :path, description: "The ID or URL-encoded path of the project owned by the authenticated user", required: true, schema: { oneOf: [{:type=>:string}, {:type=>:integer}] } + parameter name: "environment_id", in: :path, description: "The ID of the environment", required: true, schema: { type: :integer } + + parameter name: :body, in: :body, schema: { + type: :object, + properties: { + external_url: { type: :string }, + slug: { type: :string }, + tier: { type: :string }, + cluster_agent_id: { type: :integer }, + kubernetes_namespace: { type: :string }, + flux_resource_path: { type: :string }, + description: { type: :string }, + auto_stop_setting: { type: :string } + }, + required: [] + } + + response '200', 'successful' do + schema type: :object, properties: { + id: { type: :string, example: 1 }, + name: { type: :string, example: "deploy" }, + slug: { type: :string, example: "deploy" }, + external_url: { type: :string, example: "https://deploy.gitlab.example.com" }, + created_at: { type: :string, example: "2019-05-25T18:55:13.252Z" }, + updated_at: { type: :string, example: "2019-05-25T18:55:13.252Z" }, + tier: { type: :string, example: "development" }, + state: { type: :string, example: "available" }, + auto_stop_at: { type: :string, example: "2019-05-25T18:55:13.252Z" }, + description: { type: :string, example: "description" }, + auto_stop_setting: { type: :string, example: "always" } + } + run_test! + end + response '400', 'bad request' do + run_test! + end + response '401', 'unauthorized' do + run_test! + end + response '404', 'not found' do + run_test! + end + end + delete 'Delete an environment' do + tags ["environments"] + produces 'application/json' + parameter name: "id", in: :path, description: "The ID or URL-encoded path of the project owned by the authenticated user", required: true, schema: { oneOf: [{:type=>:string}, {:type=>:integer}] } + parameter name: "environment_id", in: :path, description: "The ID of the environment", required: true, schema: { type: :integer } + + response '200', 'successful' do + schema type: :object, properties: { + id: { type: :string, example: 1 }, + name: { type: :string, example: "deploy" }, + slug: { type: :string, example: "deploy" }, + external_url: { type: :string, example: "https://deploy.gitlab.example.com" }, + created_at: { type: :string, example: "2019-05-25T18:55:13.252Z" }, + updated_at: { type: :string, example: "2019-05-25T18:55:13.252Z" }, + tier: { type: :string, example: "development" }, + state: { type: :string, example: "available" }, + auto_stop_at: { type: :string, example: "2019-05-25T18:55:13.252Z" }, + description: { type: :string, example: "description" }, + auto_stop_setting: { type: :string, example: "always" } + } + run_test! + end + response '401', 'unauthorized' do + run_test! + end + response '404', 'not found' do + run_test! + end + end + get 'Get a specific environment' do + tags ["environments"] + produces 'application/json' + parameter name: "id", in: :path, description: "The ID or URL-encoded path of the project owned by the authenticated user", required: true, schema: { oneOf: [{:type=>:string}, {:type=>:integer}] } + parameter name: "environment_id", in: :path, description: "The ID of the environment", required: true, schema: { type: :integer } + + response '200', 'successful' do + schema type: :object, properties: { + id: { type: :string, example: 1 }, + name: { type: :string, example: "deploy" }, + slug: { type: :string, example: "deploy" }, + external_url: { type: :string, example: "https://deploy.gitlab.example.com" }, + created_at: { type: :string, example: "2019-05-25T18:55:13.252Z" }, + updated_at: { type: :string, example: "2019-05-25T18:55:13.252Z" }, + tier: { type: :string, example: "development" }, + state: { type: :string, example: "available" }, + auto_stop_at: { type: :string, example: "2019-05-25T18:55:13.252Z" }, + description: { type: :string, example: "description" }, + auto_stop_setting: { type: :string, example: "always" } + } + run_test! + end + response '401', 'unauthorized' do + run_test! + end + response '404', 'not found' do + run_test! + end + end + end + + + path "/api/v4/projects/{id}/environments/review_apps" do + delete 'Delete multiple stopped review apps' do + tags ["environments"] + produces 'application/json' + parameter name: "id", in: :path, description: "The ID or URL-encoded path of the project owned by the authenticated user", required: true, schema: { oneOf: [{:type=>:string}, {:type=>:integer}] } + + response '200', 'successful' do + schema type: :object, properties: { + id: { type: :string, example: 1 }, + name: { type: :string, example: "deploy" }, + slug: { type: :string, example: "deploy" }, + external_url: { type: :string, example: "https://deploy.gitlab.example.com" }, + created_at: { type: :string, example: "2019-05-25T18:55:13.252Z" }, + updated_at: { type: :string, example: "2019-05-25T18:55:13.252Z" } + } + run_test! + end + response '400', 'bad request' do + run_test! + end + response '401', 'unauthorized' do + run_test! + end + response '404', 'not found' do + run_test! + end + response '409', 'conflict' do + run_test! + end + end + end + + + path "/api/v4/projects/{id}/environments/{environment_id}/stop" do + post 'Stop an environment' do + tags ["environments"] + produces 'application/json' + consumes 'application/json' + parameter name: "id", in: :path, description: "The ID or URL-encoded path of the project owned by the authenticated user", required: true, schema: { oneOf: [{:type=>:string}, {:type=>:integer}] } + parameter name: "environment_id", in: :path, description: "The ID of the environment", required: true, schema: { type: :integer } + + parameter name: :body, in: :body, schema: { + type: :object, + properties: { + force: { type: :string } + }, + required: [] + } + + response '200', 'successful' do + schema type: :object, properties: { + id: { type: :string, example: 1 }, + name: { type: :string, example: "deploy" }, + slug: { type: :string, example: "deploy" }, + external_url: { type: :string, example: "https://deploy.gitlab.example.com" }, + created_at: { type: :string, example: "2019-05-25T18:55:13.252Z" }, + updated_at: { type: :string, example: "2019-05-25T18:55:13.252Z" }, + tier: { type: :string, example: "development" }, + state: { type: :string, example: "available" }, + auto_stop_at: { type: :string, example: "2019-05-25T18:55:13.252Z" }, + description: { type: :string, example: "description" }, + auto_stop_setting: { type: :string, example: "always" } + } + run_test! + end + response '400', 'bad request' do + run_test! + end + response '401', 'unauthorized' do + run_test! + end + response '404', 'not found' do + run_test! + end + end + end + + + path "/api/v4/projects/{id}/environments/stop_stale" do + post 'Stop stale environments' do + tags ["environments"] + produces 'application/json' + consumes 'application/json' + parameter name: "id", in: :path, description: "The ID or URL-encoded path of the project owned by the authenticated user", required: true, schema: { oneOf: [{:type=>:string}, {:type=>:integer}] } + + parameter name: :body, in: :body, schema: { + type: :object, + properties: { + before: { type: :string } + }, + required: [:before] + } + + response '204', 'no content' do + run_test! + end + response '400', 'bad request' do + run_test! + end + response '401', 'unauthorized' do + run_test! + end + end + end + +end diff --git a/spec/requests/rswag/events_spec.rb b/spec/requests/rswag/events_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..acc22df720ea387753937c10418123a3a34832d3 --- /dev/null +++ b/spec/requests/rswag/events_spec.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true +# rubocop:disable all -- for development + +require 'swagger_helper' + +RSpec.describe "Events API", type: :request do + + path "/api/v4/events" do + get 'List currently authenticated users eventss events' do + tags ["events"] + produces 'application/json' + parameter name: "scope", in: :query, description: "Include all events across a user’s projects", required: false, schema: { type: :string, example: "all" } + parameter name: "page", in: :query, description: "Current page number", required: false, schema: { type: :integer, example: 1, default: 1 } + parameter name: "per_page", in: :query, description: "Number of items per page", required: false, schema: { type: :integer, example: 20, default: 20 } + parameter name: "action", in: :query, description: "Event action to filter on", required: false, schema: { type: :string } + parameter name: "target_type", in: :query, description: "Event target type to filter on", required: false, schema: { type: :string, enum: ["issue", "milestone", "merge_request", "note", "project", "snippet", "user", "wiki", "design"] } + parameter name: "before", in: :query, description: "Include only events created before this date", required: false, schema: { type: :string, format: :date } + parameter name: "after", in: :query, description: "Include only events created after this date", required: false, schema: { type: :string, format: :date } + parameter name: "sort", in: :query, description: "Return events sorted in ascending and descending order", required: false, schema: { type: :string, enum: ["asc", "desc"], default: "desc" } + + response '200', 'successful' do + schema type: :array, items: { + type: :object, + properties: { + id: { type: :string, example: 1 }, + project_id: { type: :string, example: 2 }, + action_name: { type: :string, example: "closed" }, + target_id: { type: :string, example: 160 }, + target_iid: { type: :string, example: 157 }, + target_type: { type: :string, example: "Issue" }, + author_id: { type: :string, example: 25 }, + target_title: { type: :string, example: "Public project search field" }, + created_at: { type: :string, example: "2017-02-09T10:43:19.667Z" }, + imported: { type: :string }, + imported_from: { type: :string, example: "none" }, + author_username: { type: :string, example: "root" } + } + } + run_test! + end + response '401', 'unauthorized' do + run_test! + end + end + end + + + path "/api/v4/users/{id}/events" do + get 'Get the contribution events of a specified user' do + tags ["events"] + produces 'application/json' + parameter name: "id", in: :path, description: "The ID or username of the user", required: true, schema: { type: :string } + parameter name: "page", in: :query, description: "Current page number", required: false, schema: { type: :integer, example: 1, default: 1 } + parameter name: "per_page", in: :query, description: "Number of items per page", required: false, schema: { type: :integer, example: 20, default: 20 } + parameter name: "action", in: :query, description: "Event action to filter on", required: false, schema: { type: :string } + parameter name: "target_type", in: :query, description: "Event target type to filter on", required: false, schema: { type: :string, enum: ["issue", "milestone", "merge_request", "note", "project", "snippet", "user", "wiki", "design"] } + parameter name: "before", in: :query, description: "Include only events created before this date", required: false, schema: { type: :string, format: :date } + parameter name: "after", in: :query, description: "Include only events created after this date", required: false, schema: { type: :string, format: :date } + parameter name: "sort", in: :query, description: "Return events sorted in ascending and descending order", required: false, schema: { type: :string, enum: ["asc", "desc"], default: "desc" } + + response '200', 'successful' do + schema type: :array, items: { + type: :object, + properties: { + id: { type: :string, example: 1 }, + project_id: { type: :string, example: 2 }, + action_name: { type: :string, example: "closed" }, + target_id: { type: :string, example: 160 }, + target_iid: { type: :string, example: 157 }, + target_type: { type: :string, example: "Issue" }, + author_id: { type: :string, example: 25 }, + target_title: { type: :string, example: "Public project search field" }, + created_at: { type: :string, example: "2017-02-09T10:43:19.667Z" }, + imported: { type: :string }, + imported_from: { type: :string, example: "none" }, + author_username: { type: :string, example: "root" } + } + } + run_test! + end + response '404', 'not found' do + run_test! + end + end + end + +end diff --git a/spec/support/known_rspec_metadata_keys.yml b/spec/support/known_rspec_metadata_keys.yml index 07d5313e2337ce66c7aa8236b9364b24a16caebe..5e1c1c72f8b615f7993ca2653ec8349b32898ab7 100644 --- a/spec/support/known_rspec_metadata_keys.yml +++ b/spec/support/known_rspec_metadata_keys.yml @@ -169,3 +169,7 @@ - :zoekt - :zoekt_cache_disabled - :zoekt_settings_enabled +- :path_item +- :operation +- :response +- :rswag diff --git a/spec/swagger_helper.rb b/spec/swagger_helper.rb new file mode 100644 index 0000000000000000000000000000000000000000..3866bcfb811f633082265dba45442e7bf2ab30ee --- /dev/null +++ b/spec/swagger_helper.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.configure do |config| + # Specify a root folder where Swagger JSON files are generated + # NOTE: If you're using the rswag-api to serve API descriptions, you'll need + # to ensure that it's configured to serve Swagger from the same folder + config.openapi_root = Rails.root.join('swagger').to_s + + # Define one or more Swagger documents and provide global metadata for each one + # When you run the 'rswag:specs:swaggerize' rake task, the complete Swagger will + # be generated at the provided relative path under openapi_root + # By default, the operations defined in spec files are added to the first + # document below. You can override this behavior by adding a openapi_spec tag to the + # the root example_group in your specs, e.g. describe '...', openapi_spec: 'v2/swagger.json' + config.openapi_specs = { + 'v1/swagger.yaml' => { + openapi: '3.0.1', + info: { + title: 'API V1', + version: 'v1' + }, + paths: {}, + servers: [ + { + url: 'http://gdk.test:3000' + } + ] + } + } + + # Specify the format of the output Swagger file when running 'rswag:specs:swaggerize'. + # The openapi_specs configuration option has the filename including format in + # the key, this may want to be changed to avoid putting yaml in json files. + # Defaults to json. Accepts ':json' and ':yaml'. + config.openapi_format = :yaml +end diff --git a/swagger/v1/swagger.yaml b/swagger/v1/swagger.yaml new file mode 100644 index 0000000000000000000000000000000000000000..263dbf65babb6cff8f2af2ac9b31da23762a0866 --- /dev/null +++ b/swagger/v1/swagger.yaml @@ -0,0 +1,907 @@ +--- +openapi: 3.0.1 +info: + title: API V1 + version: v1 +paths: + "/api/v4/audit_events/{id}": + get: + summary: Get single audit event + tags: + - - audit_events + parameters: + - name: id + in: path + description: The ID of audit event + required: true + schema: + type: integer + responses: + '200': + description: successful + content: + application/json: + schema: + type: object + '404': + description: not found + "/api/v4/audit_events": + get: + summary: Get the list of audit events + tags: + - - audit_events + parameters: + - name: entity_type + in: query + description: Return list of audit events for the specified entity type + required: true + schema: + type: string + enum: + - Project + - User + - Group + - Gitlab::Audit::InstanceScope + - name: entity_id + in: query + required: false + schema: + type: integer + - name: created_after + in: query + description: Return audit events created after the specified time + required: false + schema: + type: string + format: date-time + example: '2016-01-19T09:05:50.355Z' + - name: created_before + in: query + description: Return audit events created before the specified time + required: false + schema: + type: string + format: date-time + example: '2016-01-19T09:05:50.355Z' + - name: page + in: query + description: Current page number + required: false + schema: + type: integer + example: 1 + default: 1 + - name: per_page + in: query + description: Number of items per page + required: false + schema: + type: integer + example: 20 + default: 20 + responses: + '200': + description: successful + content: + application/json: + schema: + type: array + items: + type: object + '404': + description: not found + "/api/v4/projects/{id}/environments": + get: + summary: List environments + tags: + - - environments + parameters: + - name: id + in: path + description: The ID or URL-encoded path of the project owned by the authenticated + user + required: true + schema: + oneOf: + - type: string + - type: integer + - name: page + in: query + description: Current page number + required: false + schema: + type: integer + example: 1 + default: 1 + - name: per_page + in: query + description: Number of items per page + required: false + schema: + type: integer + example: 20 + default: 20 + - name: name + in: query + description: Return the environment with this name. Mutually exclusive with + search + required: false + schema: + type: string + - name: search + in: query + description: Return list of environments matching the search criteria. Mutually + exclusive with name. Must be at least 3 characters. + required: false + schema: + type: string + - name: states + in: query + description: 'List all environments that match a specific state. Accepted + values: `available`, `stopping`, or `stopped`. If no state value given, + returns all environments' + required: false + schema: + type: string + enum: + - stopped + - stopping + - available + responses: + '200': + description: successful + content: + application/json: + schema: + type: array + items: + type: object + properties: + id: + type: string + example: 1 + name: + type: string + example: deploy + slug: + type: string + example: deploy + external_url: + type: string + example: https://deploy.gitlab.example.com + created_at: + type: string + example: '2019-05-25T18:55:13.252Z' + updated_at: + type: string + example: '2019-05-25T18:55:13.252Z' + tier: + type: string + example: development + state: + type: string + example: available + auto_stop_at: + type: string + example: '2019-05-25T18:55:13.252Z' + description: + type: string + example: description + auto_stop_setting: + type: string + example: always + '401': + description: unauthorized + '404': + description: not found + post: + summary: Create a new environment + tags: + - - environments + parameters: + - name: id + in: path + description: The ID or URL-encoded path of the project owned by the authenticated + user + required: true + schema: + oneOf: + - type: string + - type: integer + responses: + '200': + description: successful + content: + application/json: + schema: + type: object + properties: + id: + type: string + example: 1 + name: + type: string + example: deploy + slug: + type: string + example: deploy + external_url: + type: string + example: https://deploy.gitlab.example.com + created_at: + type: string + example: '2019-05-25T18:55:13.252Z' + updated_at: + type: string + example: '2019-05-25T18:55:13.252Z' + tier: + type: string + example: development + state: + type: string + example: available + auto_stop_at: + type: string + example: '2019-05-25T18:55:13.252Z' + description: + type: string + example: description + auto_stop_setting: + type: string + example: always + '400': + description: bad request + '401': + description: unauthorized + '404': + description: not found + requestBody: + content: + application/json: + schema: + type: object + properties: + name: + type: string + external_url: + type: string + slug: + type: string + tier: + type: string + cluster_agent_id: + type: integer + kubernetes_namespace: + type: string + flux_resource_path: + type: string + description: + type: string + auto_stop_setting: + type: string + required: + - name + "/api/v4/projects/{id}/environments/stop_stale": + post: + summary: Stop stale environments + tags: + - - environments + parameters: + - name: id + in: path + description: The ID or URL-encoded path of the project owned by the authenticated + user + required: true + schema: + oneOf: + - type: string + - type: integer + responses: + '204': + description: no content + '400': + description: bad request + '401': + description: unauthorized + requestBody: + content: + application/json: + schema: + type: object + properties: + before: + type: string + required: + - before + "/api/v4/projects/{id}/environments/{environment_id}": + put: + summary: Update an existing environment + tags: + - - environments + parameters: + - name: id + in: path + description: The ID or URL-encoded path of the project owned by the authenticated + user + required: true + schema: + oneOf: + - type: string + - type: integer + - name: environment_id + in: path + description: The ID of the environment + required: true + schema: + type: integer + responses: + '200': + description: successful + content: + application/json: + schema: + type: object + properties: + id: + type: string + example: 1 + name: + type: string + example: deploy + slug: + type: string + example: deploy + external_url: + type: string + example: https://deploy.gitlab.example.com + created_at: + type: string + example: '2019-05-25T18:55:13.252Z' + updated_at: + type: string + example: '2019-05-25T18:55:13.252Z' + tier: + type: string + example: development + state: + type: string + example: available + auto_stop_at: + type: string + example: '2019-05-25T18:55:13.252Z' + description: + type: string + example: description + auto_stop_setting: + type: string + example: always + '400': + description: bad request + '401': + description: unauthorized + '404': + description: not found + requestBody: + content: + application/json: + schema: + type: object + properties: + external_url: + type: string + slug: + type: string + tier: + type: string + cluster_agent_id: + type: integer + kubernetes_namespace: + type: string + flux_resource_path: + type: string + description: + type: string + auto_stop_setting: + type: string + required: [] + delete: + summary: Delete an environment + tags: + - - environments + parameters: + - name: id + in: path + description: The ID or URL-encoded path of the project owned by the authenticated + user + required: true + schema: + oneOf: + - type: string + - type: integer + - name: environment_id + in: path + description: The ID of the environment + required: true + schema: + type: integer + responses: + '200': + description: successful + content: + application/json: + schema: + type: object + properties: + id: + type: string + example: 1 + name: + type: string + example: deploy + slug: + type: string + example: deploy + external_url: + type: string + example: https://deploy.gitlab.example.com + created_at: + type: string + example: '2019-05-25T18:55:13.252Z' + updated_at: + type: string + example: '2019-05-25T18:55:13.252Z' + tier: + type: string + example: development + state: + type: string + example: available + auto_stop_at: + type: string + example: '2019-05-25T18:55:13.252Z' + description: + type: string + example: description + auto_stop_setting: + type: string + example: always + '401': + description: unauthorized + '404': + description: not found + get: + summary: Get a specific environment + tags: + - - environments + parameters: + - name: id + in: path + description: The ID or URL-encoded path of the project owned by the authenticated + user + required: true + schema: + oneOf: + - type: string + - type: integer + - name: environment_id + in: path + description: The ID of the environment + required: true + schema: + type: integer + responses: + '200': + description: successful + content: + application/json: + schema: + type: object + properties: + id: + type: string + example: 1 + name: + type: string + example: deploy + slug: + type: string + example: deploy + external_url: + type: string + example: https://deploy.gitlab.example.com + created_at: + type: string + example: '2019-05-25T18:55:13.252Z' + updated_at: + type: string + example: '2019-05-25T18:55:13.252Z' + tier: + type: string + example: development + state: + type: string + example: available + auto_stop_at: + type: string + example: '2019-05-25T18:55:13.252Z' + description: + type: string + example: description + auto_stop_setting: + type: string + example: always + '401': + description: unauthorized + '404': + description: not found + "/api/v4/projects/{id}/environments/{environment_id}/stop": + post: + summary: Stop an environment + tags: + - - environments + parameters: + - name: id + in: path + description: The ID or URL-encoded path of the project owned by the authenticated + user + required: true + schema: + oneOf: + - type: string + - type: integer + - name: environment_id + in: path + description: The ID of the environment + required: true + schema: + type: integer + responses: + '200': + description: successful + content: + application/json: + schema: + type: object + properties: + id: + type: string + example: 1 + name: + type: string + example: deploy + slug: + type: string + example: deploy + external_url: + type: string + example: https://deploy.gitlab.example.com + created_at: + type: string + example: '2019-05-25T18:55:13.252Z' + updated_at: + type: string + example: '2019-05-25T18:55:13.252Z' + tier: + type: string + example: development + state: + type: string + example: available + auto_stop_at: + type: string + example: '2019-05-25T18:55:13.252Z' + description: + type: string + example: description + auto_stop_setting: + type: string + example: always + '400': + description: bad request + '401': + description: unauthorized + '404': + description: not found + requestBody: + content: + application/json: + schema: + type: object + properties: + force: + type: string + required: [] + "/api/v4/projects/{id}/environments/review_apps": + delete: + summary: Delete multiple stopped review apps + tags: + - - environments + parameters: + - name: id + in: path + description: The ID or URL-encoded path of the project owned by the authenticated + user + required: true + schema: + oneOf: + - type: string + - type: integer + responses: + '200': + description: successful + content: + application/json: + schema: + type: object + properties: + id: + type: string + example: 1 + name: + type: string + example: deploy + slug: + type: string + example: deploy + external_url: + type: string + example: https://deploy.gitlab.example.com + created_at: + type: string + example: '2019-05-25T18:55:13.252Z' + updated_at: + type: string + example: '2019-05-25T18:55:13.252Z' + '400': + description: bad request + '401': + description: unauthorized + '404': + description: not found + '409': + description: conflict + "/api/v4/users/{id}/events": + get: + summary: Get the contribution events of a specified user + tags: + - - events + parameters: + - name: id + in: path + description: The ID or username of the user + required: true + schema: + type: string + - name: page + in: query + description: Current page number + required: false + schema: + type: integer + example: 1 + default: 1 + - name: per_page + in: query + description: Number of items per page + required: false + schema: + type: integer + example: 20 + default: 20 + - name: action + in: query + description: Event action to filter on + required: false + schema: + type: string + - name: target_type + in: query + description: Event target type to filter on + required: false + schema: + type: string + enum: + - issue + - milestone + - merge_request + - note + - project + - snippet + - user + - wiki + - design + - name: before + in: query + description: Include only events created before this date + required: false + schema: + type: string + format: date + - name: after + in: query + description: Include only events created after this date + required: false + schema: + type: string + format: date + - name: sort + in: query + description: Return events sorted in ascending and descending order + required: false + schema: + type: string + enum: + - asc + - desc + default: desc + responses: + '200': + description: successful + content: + application/json: + schema: + type: array + items: + type: object + properties: + id: + type: string + example: 1 + project_id: + type: string + example: 2 + action_name: + type: string + example: closed + target_id: + type: string + example: 160 + target_iid: + type: string + example: 157 + target_type: + type: string + example: Issue + author_id: + type: string + example: 25 + target_title: + type: string + example: Public project search field + created_at: + type: string + example: '2017-02-09T10:43:19.667Z' + imported: + type: string + imported_from: + type: string + example: none + author_username: + type: string + example: root + '404': + description: not found + "/api/v4/events": + get: + summary: List currently authenticated users eventss events + tags: + - - events + parameters: + - name: scope + in: query + description: Include all events across a user’s projects + required: false + schema: + type: string + example: all + - name: page + in: query + description: Current page number + required: false + schema: + type: integer + example: 1 + default: 1 + - name: per_page + in: query + description: Number of items per page + required: false + schema: + type: integer + example: 20 + default: 20 + - name: action + in: query + description: Event action to filter on + required: false + schema: + type: string + - name: target_type + in: query + description: Event target type to filter on + required: false + schema: + type: string + enum: + - issue + - milestone + - merge_request + - note + - project + - snippet + - user + - wiki + - design + - name: before + in: query + description: Include only events created before this date + required: false + schema: + type: string + format: date + - name: after + in: query + description: Include only events created after this date + required: false + schema: + type: string + format: date + - name: sort + in: query + description: Return events sorted in ascending and descending order + required: false + schema: + type: string + enum: + - asc + - desc + default: desc + responses: + '200': + description: successful + content: + application/json: + schema: + type: array + items: + type: object + properties: + id: + type: string + example: 1 + project_id: + type: string + example: 2 + action_name: + type: string + example: closed + target_id: + type: string + example: 160 + target_iid: + type: string + example: 157 + target_type: + type: string + example: Issue + author_id: + type: string + example: 25 + target_title: + type: string + example: Public project search field + created_at: + type: string + example: '2017-02-09T10:43:19.667Z' + imported: + type: string + imported_from: + type: string + example: none + author_username: + type: string + example: root + '401': + description: unauthorized +servers: +- url: http://gdk.test:3000