diff --git a/frontend/models/module.ts b/frontend/models/module.ts index 555b380..538ff89 100644 --- a/frontend/models/module.ts +++ b/frontend/models/module.ts @@ -1,13 +1,29 @@ +import { MethodId } from './methodId' + export type Module = string export type SpecificModule = { module: Module + moduleDependencies: Module[] + moduleReverseDependencies: Module[] sources: Array<{ sourceName: string + module: Module memo: string + dependencies: Array<{ + sourceName: string + module: Module | null + methodIds: MethodId[] + }> }> - relatedDefinitions: Array<{ - id: number - title: string + sourceReverseDependencies: Array<{ + sourceName: string + module: Module | null + memo: string + dependencies: Array<{ + sourceName: string + module: Module + methodIds: MethodId[] + }> }> } diff --git a/frontend/pages/Modules/Show.tsx b/frontend/pages/Modules/Show.tsx index c898751..0b3c161 100644 --- a/frontend/pages/Modules/Show.tsx +++ b/frontend/pages/Modules/Show.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react' +import React from 'react' import { useParams } from 'react-router-dom' import styled from 'styled-components' @@ -7,22 +7,11 @@ import { Loading } from '@/components/Loading' import { Cluster, EmptyTableBody, Heading, Section, Stack, Table, Td, Text, Th } from '@/components/ui' import { path } from '@/constants/path' import { spacing } from '@/constants/theme' -import { KEY } from '@/hooks/useBitIdHash' import { useModule } from '@/repositories/moduleRepository' -import { encode, idsToBitId } from '@/utils/bitId' -import { stringify } from '@/utils/queryString' export const Show: React.FC = () => { const pathModule = useParams()['*'] ?? '' - const { data: specificModule, isLoading } = useModule(pathModule) - - const relatedDefinitionIds = useMemo(() => { - if (specificModule) { - return specificModule.relatedDefinitions.map(({ id }) => id) - } else { - return [] - } - }, [specificModule]) + const { data, isLoading } = useModule(pathModule) return ( @@ -44,32 +33,30 @@ export const Show: React.FC = () => { - {specificModule && !isLoading ? ( + {data && !isLoading ? ( <>
- Sources + Module Dependencies ({data.moduleDependencies.length})
- - + - {specificModule.sources.length === 0 ? ( + {data.sources.length === 0 ? ( - no sources + No module dependencies ) : ( - {specificModule.sources.map((source) => ( - - + {data.moduleDependencies.map((module) => ( + ))} @@ -82,31 +69,26 @@ export const Show: React.FC = () => {
- - Related Definitions - - Select All - - + Module Reverse Dependencies ({data.moduleReverseDependencies.length})
Source NameMemoModule
- {source.sourceName} -
- {source.memo} + + {module} +
- + - {specificModule.relatedDefinitions.length === 0 ? ( + {data.sources.length === 0 ? ( - no related definitions + No module reverse dependencies ) : ( - {specificModule.relatedDefinitions.map((relatedDefinition) => ( - + {data.moduleReverseDependencies.map((module) => ( + ))} @@ -116,6 +98,126 @@ export const Show: React.FC = () => { + +
+ + Sources ({data.sources.length}) +
+
TitleModule
- - {relatedDefinition.title} - + + {module} +
+ + + + + + + + + + {data.sources.length === 0 ? ( + + No sources + + ) : ( + + {data.sources.map((source) => + source.dependencies.map((dependency) => + dependency.methodIds.map((methodId) => ( + + + + + + + + )), + ), + )} + + )} +
SourceDependency ModuleDependencyMethod IdPath
+ + {source.sourceName} + + + {dependency.module && ( + + {dependency.module} + + )} + + + {dependency.sourceName} + + {`${methodId.context === 'class' ? '.' : '#'}${methodId.name}`} + {methodId.paths.map((methodIdPath) => ( +
+ {methodIdPath} +
+ ))} +
+
+
+
+ +
+ + Source Reverse Dependencies ({data.sourceReverseDependencies.length}) +
+ + + + + + + + + + + {data.sourceReverseDependencies.length === 0 ? ( + + No sources + + ) : ( + + {data.sourceReverseDependencies.map((source) => + source.dependencies.map((dependency) => + dependency.methodIds.map((methodId) => ( + + + + + + + + )), + ), + )} + + )} +
SourceDependency ModuleDependencyMethod IdPath
+ + {source.sourceName} + + + {dependency.module && ( + + {dependency.module} + + )} + + + {dependency.sourceName} + + {`${methodId.context === 'class' ? '.' : '#'}${methodId.name}`} + {methodId.paths.map((methodIdPath) => ( +
+ {methodIdPath} +
+ ))} +
+
+
+
) : ( diff --git a/frontend/pages/Sources/Show.tsx b/frontend/pages/Sources/Show.tsx index 69e6bb9..40c7dbf 100644 --- a/frontend/pages/Sources/Show.tsx +++ b/frontend/pages/Sources/Show.tsx @@ -70,7 +70,7 @@ export const Show: React.FC = () => { - + {specificSource.module === null ? ( @@ -100,7 +100,7 @@ export const Show: React.FC = () => {
Module NameModule
- + {specificSource.module === null ? ( @@ -169,7 +169,7 @@ export const Show: React.FC = () => {
Module NameModule
- + diff --git a/frontend/repositories/moduleRepository.ts b/frontend/repositories/moduleRepository.ts index bf7a77a..56a9a1d 100644 --- a/frontend/repositories/moduleRepository.ts +++ b/frontend/repositories/moduleRepository.ts @@ -20,13 +20,35 @@ export const useModules = () => { type SpecificModuleResponse = { module: string - related_definitions: Array<{ - id: number - title: string - }> + module_dependencies: string[] + module_reverse_dependencies: string[] sources: Array<{ source_name: string + module: string + memo: string + dependencies: Array<{ + source_name: string + module: string | null + method_ids: Array<{ + context: 'instance' | 'class' + name: string + paths: string[] + }> + }> + }> + source_reverse_dependencies: Array<{ + source_name: string + module: string | null memo: string + dependencies: Array<{ + source_name: string + module: string + method_ids: Array<{ + context: 'instance' | 'class' + name: string + paths: string[] + }> + }> }> } @@ -36,13 +58,35 @@ export const useModule = (module: string) => { return { module: response.module, + moduleDependencies: response.module_dependencies, + moduleReverseDependencies: response.module_reverse_dependencies, sources: response.sources.map((source) => ({ sourceName: source.source_name, + module: source.module, memo: source.memo, + dependencies: source.dependencies.map((dependency) => ({ + sourceName: dependency.source_name, + module: dependency.module, + methodIds: dependency.method_ids.map((methodId) => ({ + context: methodId.context, + name: methodId.name, + paths: methodId.paths, + })), + })), })), - relatedDefinitions: response.related_definitions.map((definition) => ({ - id: definition.id, - title: definition.title, + sourceReverseDependencies: response.source_reverse_dependencies.map((source) => ({ + sourceName: source.source_name, + module: source.module, + memo: source.memo, + dependencies: source.dependencies.map((dependency) => ({ + sourceName: dependency.source_name, + module: dependency.module, + methodIds: dependency.method_ids.map((methodId) => ({ + context: methodId.context, + name: methodId.name, + paths: methodId.paths, + })), + })), })), } }) diff --git a/lib/diver_down/web.rb b/lib/diver_down/web.rb index cf592a7..34677ae 100644 --- a/lib/diver_down/web.rb +++ b/lib/diver_down/web.rb @@ -15,6 +15,7 @@ class Web require 'diver_down/web/indented_string_io' require 'diver_down/web/definition_store' require 'diver_down/web/definition_loader' + require 'diver_down/web/definition_module_dependencies' require 'diver_down/web/source_alias_resolver' require 'diver_down/web/module_sources_filter' @@ -35,6 +36,7 @@ def initialize(definition_dir:, metadata:) definition_files = ::Dir["#{definition_dir}/**/*.{yml,yaml,msgpack,json}"].sort @total_definition_files_size = definition_files.size + @action = nil load_definition_files_on_thread(definition_files) end @@ -43,11 +45,14 @@ def initialize(definition_dir:, metadata:) # @return [Array[Integer, Hash, Array]] def call(env) request = Rack::Request.new(env) - action = DiverDown::Web::Action.new(store: self.class.store, metadata: @metadata, request:) + + if @action&.store.object_id != self.class.store.object_id + @action = DiverDown::Web::Action.new(store: self.class.store, metadata: @metadata) + end case [request.request_method, request.path] in ['GET', %r{\A/api/definitions\.json\z}] - action.definitions( + @action.definitions( page: request.params['page']&.to_i || 1, per: request.params['per']&.to_i || 100, title: request.params['title'] || '', @@ -55,51 +60,51 @@ def call(env) definition_group: request.params['definition_group'] || '' ) in ['GET', %r{\A/api/sources\.json\z}] - action.sources + @action.sources in ['GET', %r{\A/api/modules\.json\z}] - action.modules + @action.modules in ['GET', %r{\A/api/modules/(?[^/]+)\.json\z}] modulee = CGI.unescape(Regexp.last_match[:modulee]) - action.module(modulee) + @action.module(modulee) in ['GET', %r{\A/api/module_definitions/(?[^/]+)\.json\z}] modulee = CGI.unescape(Regexp.last_match[:modulee]) compound = request.params['compound'] == '1' concentrate = request.params['concentrate'] == '1' only_module = request.params['only_module'] == '1' - action.module_definition(compound, concentrate, only_module, modulee) + @action.module_definition(compound, concentrate, only_module, modulee) in ['GET', %r{\A/api/definitions/(?\d+)\.json\z}] bit_id = Regexp.last_match[:bit_id].to_i compound = request.params['compound'] == '1' concentrate = request.params['concentrate'] == '1' only_module = request.params['only_module'] == '1' - action.combine_definitions(bit_id, compound, concentrate, only_module) + @action.combine_definitions(bit_id, compound, concentrate, only_module) in ['GET', %r{\A/api/sources/(?[^/]+)\.json\z}] source = Regexp.last_match[:source] - action.source(source) + @action.source(source) in ['POST', %r{\A/api/sources/(?[^/]+)/module.json\z}] source = Regexp.last_match[:source] modulee = request.params['module'] || '' - action.set_module(source, modulee) + @action.set_module(source, modulee) in ['POST', %r{\A/api/sources/(?[^/]+)/memo.json\z}] source = Regexp.last_match[:source] memo = request.params['memo'] || '' - action.set_memo(source, memo) + @action.set_memo(source, memo) in ['GET', %r{\A/api/pid\.json\z}] - action.pid + @action.pid in ['GET', %r{\A/api/initialization_status\.json\z}] - action.initialization_status(@total_definition_files_size) + @action.initialization_status(@total_definition_files_size) in ['GET', %r{\A/api/source_aliases\.json\z}] - action.source_aliases + @action.source_aliases in ['POST', %r{\A/api/source_aliases\.json\z}] alias_name = request.params['alias_name'] old_alias_name = request.params['old_alias_name'] # NOTE: nillable source_names = request.params['source_names'] || [] - action.update_source_alias(alias_name, old_alias_name, source_names) + @action.update_source_alias(alias_name, old_alias_name, source_names) in ['GET', %r{\A/assets/}] @files_server.call(env) in ['GET', /\.json\z/], ['POST', /\.json\z/] - action.not_found + @action.not_found else @files_server.call(env.merge('PATH_INFO' => '/index.html')) end diff --git a/lib/diver_down/web/action.rb b/lib/diver_down/web/action.rb index edb68e4..78c453b 100644 --- a/lib/diver_down/web/action.rb +++ b/lib/diver_down/web/action.rb @@ -12,14 +12,18 @@ class Action :per ) + M = Mutex.new + + attr_reader :store + # @param store [DiverDown::Definition::Store] # @param metadata [DiverDown::Web::Metadata] - # @param request [Rack::Request] - def initialize(store:, metadata:, request:) + def initialize(store:, metadata:) @store = store @metadata = metadata @source_alias_resolver = DiverDown::Web::SourceAliasResolver.new(@metadata.source_alias) - @request = request + @module_dependency_map = nil + @module_dependency_map_cache_id = nil end # GET /api/source_aliases.json @@ -100,39 +104,56 @@ def modules # GET /api/modules/:modulee.json # @param modulee [String] def module(modulee) - # Hash{ DiverDown::Definition::Modulee => Set } - related_definition_store_ids = Set.new - source_names = Set.new - - # rubocop:disable Style/HashEachMethods - @store.each do |_, definition| - definition.sources.each do |source| - next unless @metadata.source(source.source_name).module == modulee - - source_names.add(source.source_name) - related_definition_store_ids.add(definition.store_id) - end - end - # rubocop:enable Style/HashEachMethods + module_dependency_map = fetch_module_dependency_map - if related_definition_store_ids.empty? + unless module_dependency_map.key?(modulee) return not_found end - related_definitions = related_definition_store_ids.map { @store.get(_1) } + module_dependency = module_dependency_map.fetch(modulee) json( module: modulee, - sources: source_names.sort.map do |source_name| + module_dependencies: module_dependency.module_dependencies.compact.sort, + module_reverse_dependencies: module_dependency.module_reverse_dependencies.compact.sort, + sources: module_dependency.sources.map do |source| { - source_name:, - memo: @metadata.source(source_name).memo, + source_name: source.source_name, + module: @metadata.source(source.source_name).module, + memo: @metadata.source(source.source_name).memo, + dependencies: source.dependencies.map do |dependency| + { + source_name: dependency.source_name, + module: @metadata.source(dependency.source_name).module, + method_ids: dependency.method_ids.sort.map do |method_id| + { + context: method_id.context, + name: method_id.name, + paths: method_id.paths.sort, + } + end, + } + end, } end, - related_definitions: related_definitions.map do |definition| + source_reverse_dependencies: module_dependency.source_reverse_dependencies.map do |source| { - id: definition.store_id, - title: definition.title, + source_name: source.source_name, + module: @metadata.source(source.source_name).module, + memo: @metadata.source(source.source_name).memo, + dependencies: source.dependencies.map do |dependency| + { + source_name: dependency.source_name, + module: @metadata.source(dependency.source_name).module, + method_ids: dependency.method_ids.sort.map do |method_id| + { + context: method_id.context, + name: method_id.name, + paths: method_id.paths.sort, + } + end, + } + end, } end ) @@ -365,8 +386,6 @@ def not_found private - attr_reader :request, :store - def build_method_id_key(dependency, method_id) "#{dependency.source_name}-#{method_id.context}-#{method_id.name}" end @@ -433,6 +452,18 @@ def render_combined_definition(ids, definition, titles, compound:, concentrate:, end ) end + + def fetch_module_dependency_map + M.synchronize do + if @module_dependency_map_cache_id != @store.combined_definition.object_id + definition = @store.combined_definition + @module_dependency_map = DiverDown::Web::DefinitionModuleDependencies.new(@metadata, definition).build_module_dependency_map + @module_dependency_map_cache_id = definition.object_id + end + end + + @module_dependency_map + end end end end diff --git a/lib/diver_down/web/definition_module_dependencies.rb b/lib/diver_down/web/definition_module_dependencies.rb new file mode 100644 index 0000000..7f16aa0 --- /dev/null +++ b/lib/diver_down/web/definition_module_dependencies.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +module DiverDown + class Web + class DefinitionModuleDependencies + class ModuleDependency + attr_accessor :module + attr_reader :module_dependencies, :module_reverse_dependencies, :source_map, :source_reverse_dependency_map + + def initialize(modulee) + @module = modulee + @module_dependencies = Set.new + @module_reverse_dependencies = Set.new + @source_map = {} + @source_reverse_dependency_map = {} + end + + # @return [Array] + def sources + @source_map.keys.sort.map { @source_map.fetch(_1) } + end + + # @return [Array] + def source_reverse_dependencies + @source_reverse_dependency_map.keys.sort.map { @source_reverse_dependency_map.fetch(_1) } + end + end + + def initialize(metadata, definition) + @metadata = metadata + @definition = definition + end + + # @return [Hash{ String => Hash{ String => Array } }] + def build_module_dependency_map + module_dependency_map = Hash.new { |h, k| h[k] = ModuleDependency.new(k) } + + @definition.sources.each do |source| + source_module = @metadata.source(source.source_name).module + source_module_dependency = module_dependency_map[source_module] + source_module_dependency.source_map[source.source_name] ||= DiverDown::Definition::Source.new(source_name: source.source_name) + + source.dependencies.each do |dependency| + dependency_module = @metadata.source(dependency.source_name).module + + next if source_module == dependency_module + + dependency_module_dependency = module_dependency_map[dependency_module] + + # Add module dependencies + source_module_dependency.module_dependencies.add(dependency_module) + + # Add module reverse dependencies + dependency_module_dependency.module_reverse_dependencies.add(source_module) + + # Add source + definition_source = source_module_dependency.source_map[source.source_name] + definition_dependency = definition_source.find_or_build_dependency(dependency.source_name) + dependency.method_ids.each do |method_id| + definition_dependency.find_or_build_method_id(name: method_id.name, context: method_id.context).add_path(*method_id.paths) + end + + # Add source reverse dependencies + definition_source = dependency_module_dependency.source_reverse_dependency_map[dependency.source_name] ||= DiverDown::Definition::Source.new(source_name: dependency.source_name) + definition_dependency = definition_source.find_or_build_dependency(source.source_name) + dependency.method_ids.each do |method_id| + definition_dependency.find_or_build_method_id(name: method_id.name, context: method_id.context).add_path(*method_id.paths) + end + end + end + + module_dependency_map + end + end + end +end diff --git a/spec/diver_down/web_spec.rb b/spec/diver_down/web_spec.rb index ebbccd7..08b2479 100644 --- a/spec/diver_down/web_spec.rb +++ b/spec/diver_down/web_spec.rb @@ -391,7 +391,7 @@ def assert_definition_group(definition_group, expected_ids) end end - describe 'GET /api/modules/:module_name.json' do + describe 'GET /api/modules/:modulee.json' do it 'returns unknown if store is blank' do get '/api/modules/unknown.json' @@ -416,7 +416,7 @@ def assert_definition_group(definition_group, expected_ids) ] ) - ids = store.set(definition_1, definition_2) + store.set(definition_1, definition_2) metadata.source('a.rb').module = 'A' metadata.source('b.rb').module = 'A' metadata.source('a.rb').memo = 'memo' @@ -429,23 +429,20 @@ def assert_definition_group(definition_group, expected_ids) 'sources' => [ { 'source_name' => 'a.rb', + 'dependencies' => [], + 'module' => 'A', 'memo' => 'memo', }, { + 'dependencies' => [], + 'module' => 'A', 'source_name' => 'b.rb', 'memo' => '', }, ], - 'related_definitions' => [ - { - 'id' => ids[0], - 'title' => 'title', - }, - { - 'id' => ids[1], - 'title' => 'title 2', - }, - ], + 'module_dependencies' => [], + 'module_reverse_dependencies' => [], + 'source_reverse_dependencies' => [], }) end @@ -459,7 +456,7 @@ def assert_definition_group(definition_group, expected_ids) ] ) - ids = store.set(definition) + store.set(definition) metadata.source('a.rb').module = 'グローバル' metadata.source('a.rb').memo = 'memo' @@ -471,15 +468,14 @@ def assert_definition_group(definition_group, expected_ids) 'sources' => [ { 'source_name' => 'a.rb', + 'module' => 'グローバル', + 'dependencies' => [], 'memo' => 'memo', }, ], - 'related_definitions' => [ - { - 'id' => ids[0], - 'title' => 'title', - }, - ], + 'module_dependencies' => [], + 'module_reverse_dependencies' => [], + 'source_reverse_dependencies' => [], }) end end
Module NameModule Source Name Method Id Path