diff --git a/db/docs/group_features.yml b/db/docs/group_features.yml new file mode 100644 index 0000000000000000000000000000000000000000..a7b80f586ca82c85a4a9dbe8d4a8698447efe484 --- /dev/null +++ b/db/docs/group_features.yml @@ -0,0 +1,9 @@ +--- +table_name: group_features +classes: +- Groups::FeatureSetting +feature_categories: +- subgroups +description: TODO +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82017 +milestone: '14.10' diff --git a/db/docs/project_build_artifacts_size_refreshes.yml b/db/docs/project_build_artifacts_size_refreshes.yml new file mode 100644 index 0000000000000000000000000000000000000000..8f07ab9b3e1c374b3f879844a748da17e1402747 --- /dev/null +++ b/db/docs/project_build_artifacts_size_refreshes.yml @@ -0,0 +1,9 @@ +--- +table_name: project_build_artifacts_size_refreshes +classes: +- Projects::BuildArtifactsSizeRefresh +feature_categories: +- build_artifacts +description: TODO +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81306 +milestone: '14.9' diff --git a/db/docs/protected_environment_approval_rules.yml b/db/docs/protected_environment_approval_rules.yml new file mode 100644 index 0000000000000000000000000000000000000000..ea7f0e1d05d16398eafda217c62dc60799d9d18a --- /dev/null +++ b/db/docs/protected_environment_approval_rules.yml @@ -0,0 +1,9 @@ +--- +table_name: protected_environment_approval_rules +classes: +- ProtectedEnvironments::ApprovalRule +feature_categories: +- continuous_delivery +description: TODO +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82800 +milestone: '14.10' diff --git a/spec/db/docs_spec.rb b/spec/db/docs_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..20746e107fbb83cef5556d5b7d3267e84d5c079f --- /dev/null +++ b/spec/db/docs_spec.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Database Documentation' do + context 'for each table' do + let(:all_tables) do + Gitlab::Database.database_base_models.flat_map { |_, m| m.connection.tables }.sort.uniq + end + + let(:metadata_required_fields) do + %i( + feature_categories + table_name + ) + end + + let(:metadata_allowed_fields) do + metadata_required_fields + %i( + classes + description + introduced_by_url + milestone + ) + end + + let(:metadata) do + all_tables.each_with_object({}) do |table_name, hash| + next unless File.exist?(table_metadata_file_path(table_name)) + + hash[table_name] ||= load_table_metadata(table_name) + end + end + + let(:tables_without_metadata) do + all_tables.reject { |t| metadata.has_key?(t) } + end + + let(:tables_without_valid_metadata) do + metadata.select { |_, t| t.has_key?(:error) }.keys + end + + let(:tables_with_disallowed_fields) do + metadata.select { |_, t| t.has_key?(:disallowed_fields) }.keys + end + + let(:tables_with_missing_required_fields) do + metadata.select { |_, t| t.has_key?(:missing_required_fields) }.keys + end + + it 'has a metadata file' do + expect(tables_without_metadata).to be_empty, multiline_error( + 'Missing metadata files', + tables_without_metadata.map { |t| " #{table_metadata_file(t)}" } + ) + end + + it 'has a valid metadata file' do + expect(tables_without_valid_metadata).to be_empty, table_metadata_errors( + 'Table metadata files with errors', + :error, + tables_without_valid_metadata + ) + end + + it 'has a valid metadata file with allowed fields' do + expect(tables_with_disallowed_fields).to be_empty, table_metadata_errors( + 'Table metadata files with disallowed fields', + :disallowed_fields, + tables_with_disallowed_fields + ) + end + + it 'has a valid metadata file without missing fields' do + expect(tables_with_missing_required_fields).to be_empty, table_metadata_errors( + 'Table metadata files with missing fields', + :missing_required_fields, + tables_with_missing_required_fields + ) + end + end + + private + + def table_metadata_file(table_name) + File.join('db', 'docs', "#{table_name}.yml") + end + + def table_metadata_file_path(table_name) + Rails.root.join(table_metadata_file(table_name)) + end + + def load_table_metadata(table_name) + result = {} + begin + result[:metadata] = YAML.safe_load(File.read(table_metadata_file_path(table_name))).deep_symbolize_keys + + disallowed_fields = (result[:metadata].keys - metadata_allowed_fields) + unless disallowed_fields.empty? + result[:disallowed_fields] = "fields not allowed: #{disallowed_fields.join(', ')}" + end + + missing_required_fields = (metadata_required_fields - result[:metadata].reject { |_, v| v.blank? }.keys) + unless missing_required_fields.empty? + result[:missing_required_fields] = "missing required fields: #{missing_required_fields.join(', ')}" + end + rescue Psych::SyntaxError => ex + result[:error] = ex.message + end + result + end + + def table_metadata_errors(title, field, tables) + lines = tables.map do |table_name| + <<~EOM + #{table_metadata_file(table_name)} + #{metadata[table_name][field]} + EOM + end + + multiline_error(title, lines) + end + + def multiline_error(title, lines) + <<~EOM + #{title}: + + #{lines.join("\n")} + EOM + end +end