From b160efb1724f403c1426eae3eb2b9327c63e18d4 Mon Sep 17 00:00:00 2001 From: alpaca-tc Date: Wed, 3 Apr 2024 16:51:02 +0900 Subject: [PATCH 1/8] Use webrick single thread returns same store. --- exe/diver_down_web | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exe/diver_down_web b/exe/diver_down_web index 5a19ec8..4b48af8 100755 --- a/exe/diver_down_web +++ b/exe/diver_down_web @@ -12,9 +12,9 @@ begin # Rack 2.0 require 'rack' require 'rack/server' - Rack::Server.new(app:).start + Rack::Server.new(app:, server: :webrick).start rescue LoadError # Rack 3.0 require 'rackup' - Rackup::Server.new(app:).start + Rackup::Server.new(app:, server: :webrick).start end From 599a32bfd6775108b347b84354d12c90852b4d31 Mon Sep 17 00:00:00 2001 From: alpaca-tc Date: Wed, 3 Apr 2024 17:14:50 +0900 Subject: [PATCH 2/8] fix indent --- lib/diver_down/web/definition_to_dot.rb | 32 ++++++------------- spec/diver_down/web/definition_to_dot_spec.rb | 2 +- 2 files changed, 11 insertions(+), 23 deletions(-) diff --git a/lib/diver_down/web/definition_to_dot.rb b/lib/diver_down/web/definition_to_dot.rb index e31413a..68f210c 100644 --- a/lib/diver_down/web/definition_to_dot.rb +++ b/lib/diver_down/web/definition_to_dot.rb @@ -79,26 +79,16 @@ def respond_to_missing?(action, include_private = false) end # @param definition [DiverDown::Definition] - def initialize(definition) + # @param compound [Boolean] + def initialize(definition, compound: false) @definition = definition @io = DiverDown::IndentedStringIo.new @indent = 0 + @compound = compound end # @return [String] def to_s - # <<~EOS - # digraph G { - # compound=true; - # subgraph cluster0 { - # b -> d; - # } - # subgraph cluster1 { - # e -> f; - # } - # b -> e [ltail=cluster0,lhead=cluster1]; - # } - # EOS sources = definition.sources .sort_by(&:source_name) .map { SourceDecorator.new(_1) } @@ -124,13 +114,11 @@ def insert_source(source) insert_modules(source) end - io.indented do - source.dependencies.each do - attributes = build_attributes(tooltip: _1.tooltip) - io.write %("#{source.source_name}" -> "#{_1.source_name}") - io.write %( #{attributes}) if attributes - io.write "\n" - end + source.dependencies.each do + attributes = build_attributes(tooltip: _1.tooltip) + io.write %("#{source.source_name}" -> "#{_1.source_name}") + io.write %( #{attributes}) if attributes + io.write "\n" end end @@ -140,7 +128,7 @@ def insert_modules(source) # last subgraph last_module_writer = proc do - io.puts %( subgraph "cluster_#{last_module.module_name}" {), indent: false + io.puts %(#{' ' unless modules.empty?}subgraph "cluster_#{last_module.module_name}" {), indent: false io.indented do source_attributes = build_attributes(label: last_module.module_name, _wrap: false) module_attributes = build_attributes(label: source.source_name) @@ -173,9 +161,9 @@ def insert_modules(source) # rubocop:disable Lint/UnderscorePrefixedVariableName # attrsの参考 https://qiita.com/rubytomato@github/items/51779135bc4b77c8c20d - # attrs.merge!(label: 'a-b', headlabel: "head", taillabel: "tail") def build_attributes(_wrap: '[]', **attrs) attrs_str = attrs.filter_map { %(#{_1}="#{_2}") if _2 }.join(' ') + attrs.merge!(label: 'a-b', headlabel: "head", taillabel: "tail") return if attrs_str.empty? diff --git a/spec/diver_down/web/definition_to_dot_spec.rb b/spec/diver_down/web/definition_to_dot_spec.rb index 3ba520d..500deeb 100644 --- a/spec/diver_down/web/definition_to_dot_spec.rb +++ b/spec/diver_down/web/definition_to_dot_spec.rb @@ -68,7 +68,7 @@ def build_definition(title: 'title', sources: []) expect(described_class.new(definition).to_s).to eq(<<~DOT) strict digraph "title" { "a.rb" [label="a.rb"] - "a.rb" -> "b.rb" + "a.rb" -> "b.rb" } DOT end From a801fac16831fadf56ed8bd4e87be988bf3ee445 Mon Sep 17 00:00:00 2001 From: alpaca-tc Date: Wed, 3 Apr 2024 17:34:08 +0900 Subject: [PATCH 3/8] API returns compound dot --- lib/diver_down/web.rb | 3 +- lib/diver_down/web/action.rb | 5 ++- lib/diver_down/web/definition_to_dot.rb | 29 ++++++++++---- spec/diver_down/web/definition_to_dot_spec.rb | 40 +++++++++++++++++++ spec/diver_down/web_spec.rb | 19 +++++++++ 5 files changed, 86 insertions(+), 10 deletions(-) diff --git a/lib/diver_down/web.rb b/lib/diver_down/web.rb index 47906f7..6f428a3 100644 --- a/lib/diver_down/web.rb +++ b/lib/diver_down/web.rb @@ -51,7 +51,8 @@ def call(env) action.module(module_name) in ['GET', %r{\A/api/definitions/(?\d+)\.json\z}] bit_id = Regexp.last_match[:bit_id].to_i - action.combine_definitions(bit_id) + compound = request.params['compound'] == '1' + action.combine_definitions(bit_id, compound) in ['GET', %r{\A/api/sources/(?.+)\.json\z}] source = Regexp.last_match[:source] action.source(source) diff --git a/lib/diver_down/web/action.rb b/lib/diver_down/web/action.rb index 3679116..19e60a8 100644 --- a/lib/diver_down/web/action.rb +++ b/lib/diver_down/web/action.rb @@ -144,7 +144,8 @@ def pid # GET /api/definitions/:bit_id.json # # @param bit_id [Integer] - def combine_definitions(bit_id) + # @param compound [Boolean] + def combine_definitions(bit_id, compound) ids = DiverDown::Web::BitId.bit_id_to_ids(bit_id) valid_ids = ids.select do @@ -167,7 +168,7 @@ def combine_definitions(bit_id) json( titles:, bit_id: DiverDown::Web::BitId.ids_to_bit_id(valid_ids).to_s, - dot: DiverDown::Web::DefinitionToDot.new(definition).to_s, + dot: DiverDown::Web::DefinitionToDot.new(definition, compound:).to_s, sources: definition.sources.map do { source_name: _1.source_name, diff --git a/lib/diver_down/web/definition_to_dot.rb b/lib/diver_down/web/definition_to_dot.rb index 68f210c..b062035 100644 --- a/lib/diver_down/web/definition_to_dot.rb +++ b/lib/diver_down/web/definition_to_dot.rb @@ -95,6 +95,7 @@ def to_s io.puts %(strict digraph "#{definition.title}" {) io.indented do + io.puts('compound=true') if @compound sources.each do insert_source(_1) end @@ -115,10 +116,18 @@ def insert_source(source) end source.dependencies.each do - attributes = build_attributes(tooltip: _1.tooltip) - io.write %("#{source.source_name}" -> "#{_1.source_name}") - io.write %( #{attributes}) if attributes - io.write "\n" + attributes = {} + + if @compound + attributes.merge!( + lhead: module_label(*source.modules), + ltail: module_label(*definition.source(_1.source_name).modules) + ) + end + + io.write(%("#{source.source_name}" -> "#{_1.source_name}")) + io.write(%( #{build_attributes(**attributes)}), indent: false) unless attributes.empty? + io.write("\n") end end @@ -128,7 +137,7 @@ def insert_modules(source) # last subgraph last_module_writer = proc do - io.puts %(#{' ' unless modules.empty?}subgraph "cluster_#{last_module.module_name}" {), indent: false + io.puts %(#{' ' unless modules.empty?}subgraph "#{module_label(last_module)}" {), indent: false io.indented do source_attributes = build_attributes(label: last_module.module_name, _wrap: false) module_attributes = build_attributes(label: source.source_name) @@ -143,7 +152,7 @@ def insert_modules(source) # wrapper subgraph modules_writer = modules.inject(last_module_writer) do |next_writer, mod| proc do - io.puts %(subgraph "cluster_#{mod.module_name}" {) + io.puts %(subgraph "#{module_label(mod)}" {) io.indented do attributes = build_attributes(label: mod.module_name, _wrap: false) io.write attributes @@ -163,7 +172,7 @@ def insert_modules(source) # attrsの参考 https://qiita.com/rubytomato@github/items/51779135bc4b77c8c20d def build_attributes(_wrap: '[]', **attrs) attrs_str = attrs.filter_map { %(#{_1}="#{_2}") if _2 }.join(' ') - attrs.merge!(label: 'a-b', headlabel: "head", taillabel: "tail") + attrs.merge!(label: 'a-b', headlabel: 'head', taillabel: 'tail') return if attrs_str.empty? @@ -190,6 +199,12 @@ def swap_io ensure @io = old_io end + + def module_label(*modules) + return if modules.empty? + + "cluster_#{modules[0].module_name}" + end end end end diff --git a/spec/diver_down/web/definition_to_dot_spec.rb b/spec/diver_down/web/definition_to_dot_spec.rb index 500deeb..078ccf2 100644 --- a/spec/diver_down/web/definition_to_dot_spec.rb +++ b/spec/diver_down/web/definition_to_dot_spec.rb @@ -101,6 +101,46 @@ def build_definition(title: 'title', sources: []) } DOT end + + it 'returns composed digraph if composed = true' do + definition = build_definition( + sources: [ + { + source_name: 'a.rb', + modules: [ + { + module_name: 'A', + }, + ], + dependencies: [ + { + source_name: 'b.rb', + }, + ], + }, { + source_name: 'b.rb', + modules: [ + { + module_name: 'B', + }, + ], + dependencies: [], + }, + ] + ) + + expect(described_class.new(definition, compound: true).to_s).to eq(<<~DOT) + strict digraph "title" { + subgraph "cluster_A" { + label="A" "a.rb" [label="a.rb"] + } + "a.rb" -> "b.rb" [lhead="cluster_A" ltail="cluster_B"] + subgraph "cluster_B" { + label="B" "b.rb" [label="b.rb"] + } + } + DOT + end end end end diff --git a/spec/diver_down/web_spec.rb b/spec/diver_down/web_spec.rb index 03431dc..b04ee07 100644 --- a/spec/diver_down/web_spec.rb +++ b/spec/diver_down/web_spec.rb @@ -424,6 +424,25 @@ def assert_source(source, expected_ids) expect(last_response.headers['content-type']).to eq('application/json') expect(last_response.body).to include('digraph') end + + it 'returns combined definition with compound=true' do + definition = DiverDown::Definition.new( + title: 'title', + sources: [ + DiverDown::Definition::Source.new( + source_name: 'a.rb' + ), + ] + ) + bit_ids = store.set(definition) + + get "/api/definitions/#{bit_ids.inject(0, &:|)}.json", compound: '1' + + expect(last_response.status).to eq(200) + expect(last_response.headers['content-type']).to eq('application/json') + expect(last_response.body).to include('digraph') + expect(last_response.body).to include('compound') + end end describe 'GET /api/sources/:source.json' do From 91dcf6f63c189136447534a9017e960d0d2d6233 Mon Sep 17 00:00:00 2001 From: alpaca-tc Date: Wed, 3 Apr 2024 21:48:53 +0900 Subject: [PATCH 4/8] Clip dependency arrow --- frontend/pages/Home/Show.tsx | 10 ++- .../ConfigureGraphOptionsDialog.tsx | 77 +++++++++++++++++++ .../DefinitionGraph/DefinitionGraph.tsx | 30 +++++++- .../Home/components/DefinitionGraph/index.ts | 1 + .../combinedDefinitionRepository.ts | 5 +- lib/diver_down/web/definition_to_dot.rb | 12 +-- spec/diver_down/web/definition_to_dot_spec.rb | 19 ++++- 7 files changed, 139 insertions(+), 15 deletions(-) create mode 100644 frontend/pages/Home/components/DefinitionGraph/ConfigureGraphOptionsDialog.tsx diff --git a/frontend/pages/Home/Show.tsx b/frontend/pages/Home/Show.tsx index f549085..ddb2101 100644 --- a/frontend/pages/Home/Show.tsx +++ b/frontend/pages/Home/Show.tsx @@ -5,15 +5,19 @@ import { Loading } from '@/components/Loading' import { Aside, Section, Sidebar, Stack } from '@/components/ui' import { color } from '@/constants/theme' import { useBitIdHash } from '@/hooks/useBitIdHash' +import { useLocalStorage } from '@/hooks/useLocalStorage' import { useCombinedDefinition } from '@/repositories/combinedDefinitionRepository' -import { DefinitionGraph } from './components/DefinitionGraph' +import { DefinitionGraph, GraphOptions } from './components/DefinitionGraph' import { DefinitionList } from './components/DefinitionList' import { DefinitionSources } from './components/DefinitionSources' export const Show: React.FC = () => { const [selectedDefinitionIds, setSelectedDefinitionIds] = useBitIdHash() - const { data: combinedDefinition, isLoading } = useCombinedDefinition(selectedDefinitionIds) + const [graphOptions, setGraphOptions] = useLocalStorage('HomeShow-GraphOptions', { compound: false }) + const { data: combinedDefinition, isLoading } = useCombinedDefinition(selectedDefinitionIds, graphOptions.compound) + + console.log({ graphOptions }) return ( @@ -32,7 +36,7 @@ export const Show: React.FC = () => { ) : ( - + )} diff --git a/frontend/pages/Home/components/DefinitionGraph/ConfigureGraphOptionsDialog.tsx b/frontend/pages/Home/components/DefinitionGraph/ConfigureGraphOptionsDialog.tsx new file mode 100644 index 0000000..5ea4db3 --- /dev/null +++ b/frontend/pages/Home/components/DefinitionGraph/ConfigureGraphOptionsDialog.tsx @@ -0,0 +1,77 @@ +import React, { useCallback, useState } from 'react' +import styled from 'styled-components' + +import { ActionDialog, CheckBox, FormControl, Section, Stack } from '@/components/ui' +import { spacing } from '@/constants/theme' + +export type GraphOptions = { + compound: boolean +} + +type Props = { + isOpen: boolean + onClickClose: () => void + graphOptions: GraphOptions + setGraphOptions: React.Dispatch> +} + +export const ConfigureViewOptionsDialog: React.FC = ({ + isOpen, + onClickClose, + graphOptions, + setGraphOptions, +}) => { + const [temporaryViewOptions, setTemporaryViewOptions] = + useState(graphOptions) + + const handleDialogClose = () => { + onClickClose() + + // reset + setTemporaryViewOptions(graphOptions) + } + + const handleSubmit = () => { + setGraphOptions(temporaryViewOptions) + onClickClose() + } + + const onChangeCompound = useCallback( + (event: React.ChangeEvent) => { + setTemporaryViewOptions((prev) => ({ ...prev, compound: event.target.checked })) + }, + [setTemporaryViewOptions], + ) + + return ( + 'Close' }} + actionText="Save" + actionTheme="primary" + isOpen={isOpen} + onClickAction={handleSubmit} + onClickClose={handleDialogClose} + onClickOverlay={handleDialogClose} + width={'500px'} + > + + + +

Configure settings related to the display of definitions.

+ + + + + + +
+
+
+
+ ) +} + +const WrapperSection = styled(Section)` + padding: ${spacing.XS}; +` diff --git a/frontend/pages/Home/components/DefinitionGraph/DefinitionGraph.tsx b/frontend/pages/Home/components/DefinitionGraph/DefinitionGraph.tsx index b321502..3a89352 100644 --- a/frontend/pages/Home/components/DefinitionGraph/DefinitionGraph.tsx +++ b/frontend/pages/Home/components/DefinitionGraph/DefinitionGraph.tsx @@ -1,18 +1,24 @@ -import { FC, useEffect, useState } from 'react' +import { FC, useCallback, useEffect, useState } from 'react' import styled from 'styled-components' -import { Heading, LineClamp, Section, Text } from '@/components/ui' +import { Button, FaGearIcon, Heading, LineClamp, Section, Text } from '@/components/ui' import { color } from '@/constants/theme' import { CombinedDefinition } from '@/models/combinedDefinition' import { renderDot } from '@/utils/renderDot' +import { ConfigureViewOptionsDialog, GraphOptions } from './ConfigureGraphOptionsDialog' import { ScrollableSvg } from './ScrollableSvg' type Props = { combinedDefinition: CombinedDefinition + graphOptions: GraphOptions + setGraphOptions: React.Dispatch> } -export const DefinitionGraph: FC = ({ combinedDefinition }) => { +type DialogType = 'configureViewOptionsDiaglog' + +export const DefinitionGraph: FC = ({ combinedDefinition, graphOptions, setGraphOptions }) => { + const [visibleDialog, setVisibleDialog] = useState(null) const [svg, setSvg] = useState('') useEffect(() => { @@ -28,8 +34,18 @@ export const DefinitionGraph: FC = ({ combinedDefinition }) => { loadSvg() }, [combinedDefinition.dot, setSvg]) + const onClickCloseDialog = useCallback(() => { + setVisibleDialog(null) + }, [setVisibleDialog]) + return ( + {combinedDefinition.titles.map((title, index) => ( @@ -38,6 +54,14 @@ export const DefinitionGraph: FC = ({ combinedDefinition }) => { ))} + diff --git a/frontend/pages/Home/components/DefinitionGraph/index.ts b/frontend/pages/Home/components/DefinitionGraph/index.ts index 3836adf..da6b236 100644 --- a/frontend/pages/Home/components/DefinitionGraph/index.ts +++ b/frontend/pages/Home/components/DefinitionGraph/index.ts @@ -1 +1,2 @@ export { DefinitionGraph } from './DefinitionGraph' +export type { GraphOptions } from "./ConfigureGraphOptionsDialog" diff --git a/frontend/repositories/combinedDefinitionRepository.ts b/frontend/repositories/combinedDefinitionRepository.ts index 4e0a4ca..71785be 100644 --- a/frontend/repositories/combinedDefinitionRepository.ts +++ b/frontend/repositories/combinedDefinitionRepository.ts @@ -3,6 +3,7 @@ import useSWR from 'swr' import { path } from '@/constants/path' import { CombinedDefinition } from '@/models/combinedDefinition' import { bitIdToIds } from '@/utils/bitId' +import { stringify } from '@/utils/queryString' import { get } from './httpRequest' @@ -34,8 +35,8 @@ const fetchDefinitionShow = async (requestPath: string): Promise { - const requestPath = path.api.definitions.show(ids) +export const useCombinedDefinition = (ids: number[], compound: boolean) => { + const requestPath = `${path.api.definitions.show(ids)}?${stringify({ compound: compound ? '1' : null })}` const shouldFetch = ids.length > 0 const { data, isLoading } = useSWR(shouldFetch ? requestPath : null, fetchDefinitionShow) diff --git a/lib/diver_down/web/definition_to_dot.rb b/lib/diver_down/web/definition_to_dot.rb index b062035..30a72c2 100644 --- a/lib/diver_down/web/definition_to_dot.rb +++ b/lib/diver_down/web/definition_to_dot.rb @@ -3,6 +3,8 @@ module DiverDown class Web class DefinitionToDot + ATTRIBUTE_DELIMITER = ' ' + class SourceDecorator < BasicObject attr_reader :dependencies @@ -119,10 +121,10 @@ def insert_source(source) attributes = {} if @compound - attributes.merge!( - lhead: module_label(*source.modules), - ltail: module_label(*definition.source(_1.source_name).modules) - ) + ltail = module_label(*source.modules) + lhead = module_label(*definition.source(_1.source_name).modules) + + attributes.merge!(ltail:, lhead:) end io.write(%("#{source.source_name}" -> "#{_1.source_name}")) @@ -171,7 +173,7 @@ def insert_modules(source) # rubocop:disable Lint/UnderscorePrefixedVariableName # attrsの参考 https://qiita.com/rubytomato@github/items/51779135bc4b77c8c20d def build_attributes(_wrap: '[]', **attrs) - attrs_str = attrs.filter_map { %(#{_1}="#{_2}") if _2 }.join(' ') + attrs_str = attrs.filter_map { %(#{_1}="#{_2}") if _2 }.join(ATTRIBUTE_DELIMITER) attrs.merge!(label: 'a-b', headlabel: 'head', taillabel: 'tail') return if attrs_str.empty? diff --git a/spec/diver_down/web/definition_to_dot_spec.rb b/spec/diver_down/web/definition_to_dot_spec.rb index 078ccf2..c4667b2 100644 --- a/spec/diver_down/web/definition_to_dot_spec.rb +++ b/spec/diver_down/web/definition_to_dot_spec.rb @@ -102,7 +102,7 @@ def build_definition(title: 'title', sources: []) DOT end - it 'returns composed digraph if composed = true' do + it 'returns compound digraph if compound = true' do definition = build_definition( sources: [ { @@ -115,6 +115,8 @@ def build_definition(title: 'title', sources: []) dependencies: [ { source_name: 'b.rb', + }, { + source_name: 'c.rb', }, ], }, { @@ -125,19 +127,32 @@ def build_definition(title: 'title', sources: []) }, ], dependencies: [], + }, { + source_name: 'c.rb', + modules: [ + { + module_name: 'B', + }, + ], + dependencies: [], }, ] ) expect(described_class.new(definition, compound: true).to_s).to eq(<<~DOT) strict digraph "title" { + compound=true subgraph "cluster_A" { label="A" "a.rb" [label="a.rb"] } - "a.rb" -> "b.rb" [lhead="cluster_A" ltail="cluster_B"] + "a.rb" -> "b.rb" [ltail="cluster_A" lhead="cluster_B"] + "a.rb" -> "c.rb" [ltail="cluster_A" lhead="cluster_B"] subgraph "cluster_B" { label="B" "b.rb" [label="b.rb"] } + subgraph "cluster_B" { + label="B" "c.rb" [label="c.rb"] + } } DOT end From 5c20da041142db9ec49680ca0522350f9896b0c3 Mon Sep 17 00:00:00 2001 From: alpaca-tc Date: Wed, 3 Apr 2024 23:01:08 +0900 Subject: [PATCH 5/8] Add concentrate option. --- frontend/pages/Home/Show.tsx | 6 ++--- .../ConfigureGraphOptionsDialog.tsx | 14 +++++++++- .../combinedDefinitionRepository.ts | 10 +++++-- lib/diver_down/web.rb | 3 ++- lib/diver_down/web/action.rb | 5 ++-- lib/diver_down/web/definition_to_dot.rb | 5 +++- spec/diver_down/web/definition_to_dot_spec.rb | 27 ++++++++++++------- 7 files changed, 50 insertions(+), 20 deletions(-) diff --git a/frontend/pages/Home/Show.tsx b/frontend/pages/Home/Show.tsx index ddb2101..931103d 100644 --- a/frontend/pages/Home/Show.tsx +++ b/frontend/pages/Home/Show.tsx @@ -14,10 +14,8 @@ import { DefinitionSources } from './components/DefinitionSources' export const Show: React.FC = () => { const [selectedDefinitionIds, setSelectedDefinitionIds] = useBitIdHash() - const [graphOptions, setGraphOptions] = useLocalStorage('HomeShow-GraphOptions', { compound: false }) - const { data: combinedDefinition, isLoading } = useCombinedDefinition(selectedDefinitionIds, graphOptions.compound) - - console.log({ graphOptions }) + const [graphOptions, setGraphOptions] = useLocalStorage('HomeShow-GraphOptions', { compound: false, concentrate: false }) + const { data: combinedDefinition, isLoading } = useCombinedDefinition(selectedDefinitionIds, graphOptions.compound, graphOptions.concentrate) return ( diff --git a/frontend/pages/Home/components/DefinitionGraph/ConfigureGraphOptionsDialog.tsx b/frontend/pages/Home/components/DefinitionGraph/ConfigureGraphOptionsDialog.tsx index 5ea4db3..30b7c14 100644 --- a/frontend/pages/Home/components/DefinitionGraph/ConfigureGraphOptionsDialog.tsx +++ b/frontend/pages/Home/components/DefinitionGraph/ConfigureGraphOptionsDialog.tsx @@ -6,6 +6,7 @@ import { spacing } from '@/constants/theme' export type GraphOptions = { compound: boolean + concentrate: boolean } type Props = { @@ -43,6 +44,13 @@ export const ConfigureViewOptionsDialog: React.FC = ({ [setTemporaryViewOptions], ) + const onChangeConcentrate = useCallback( + (event: React.ChangeEvent) => { + setTemporaryViewOptions((prev) => ({ ...prev, concentrate: event.target.checked })) + }, + [setTemporaryViewOptions], + ) + return ( = ({ -

Configure settings related to the display of definitions.

+

Configure graph settings.

+ + + +
diff --git a/frontend/repositories/combinedDefinitionRepository.ts b/frontend/repositories/combinedDefinitionRepository.ts index 71785be..ea8a266 100644 --- a/frontend/repositories/combinedDefinitionRepository.ts +++ b/frontend/repositories/combinedDefinitionRepository.ts @@ -35,8 +35,14 @@ const fetchDefinitionShow = async (requestPath: string): Promise { - const requestPath = `${path.api.definitions.show(ids)}?${stringify({ compound: compound ? '1' : null })}` +const toBooleanFlag = (value: boolean) => (value ? '1' : null) + +export const useCombinedDefinition = (ids: number[], compound: boolean, concentrate: boolean) => { + const params = { + compound: toBooleanFlag(compound), + concentrate: toBooleanFlag(concentrate), + } + const requestPath = `${path.api.definitions.show(ids)}?${stringify(params)}` const shouldFetch = ids.length > 0 const { data, isLoading } = useSWR(shouldFetch ? requestPath : null, fetchDefinitionShow) diff --git a/lib/diver_down/web.rb b/lib/diver_down/web.rb index 6f428a3..2fe93c9 100644 --- a/lib/diver_down/web.rb +++ b/lib/diver_down/web.rb @@ -52,7 +52,8 @@ def call(env) in ['GET', %r{\A/api/definitions/(?\d+)\.json\z}] bit_id = Regexp.last_match[:bit_id].to_i compound = request.params['compound'] == '1' - action.combine_definitions(bit_id, compound) + concentrate = request.params['concentrate'] == '1' + action.combine_definitions(bit_id, compound, concentrate) in ['GET', %r{\A/api/sources/(?.+)\.json\z}] source = Regexp.last_match[:source] action.source(source) diff --git a/lib/diver_down/web/action.rb b/lib/diver_down/web/action.rb index 19e60a8..8bb8e6e 100644 --- a/lib/diver_down/web/action.rb +++ b/lib/diver_down/web/action.rb @@ -145,7 +145,8 @@ def pid # # @param bit_id [Integer] # @param compound [Boolean] - def combine_definitions(bit_id, compound) + # @param concentrate [Boolean] + def combine_definitions(bit_id, compound, concentrate) ids = DiverDown::Web::BitId.bit_id_to_ids(bit_id) valid_ids = ids.select do @@ -168,7 +169,7 @@ def combine_definitions(bit_id, compound) json( titles:, bit_id: DiverDown::Web::BitId.ids_to_bit_id(valid_ids).to_s, - dot: DiverDown::Web::DefinitionToDot.new(definition, compound:).to_s, + dot: DiverDown::Web::DefinitionToDot.new(definition, compound:, concentrate:).to_s, sources: definition.sources.map do { source_name: _1.source_name, diff --git a/lib/diver_down/web/definition_to_dot.rb b/lib/diver_down/web/definition_to_dot.rb index 30a72c2..0e87b31 100644 --- a/lib/diver_down/web/definition_to_dot.rb +++ b/lib/diver_down/web/definition_to_dot.rb @@ -82,11 +82,13 @@ def respond_to_missing?(action, include_private = false) # @param definition [DiverDown::Definition] # @param compound [Boolean] - def initialize(definition, compound: false) + # @param concentrate [Boolean] https://graphviz.org/docs/attrs/concentrate/ + def initialize(definition, compound: false, concentrate: false) @definition = definition @io = DiverDown::IndentedStringIo.new @indent = 0 @compound = compound + @concentrate = concentrate end # @return [String] @@ -98,6 +100,7 @@ def to_s io.puts %(strict digraph "#{definition.title}" {) io.indented do io.puts('compound=true') if @compound + io.puts('concentrate=true') if @concentrate sources.each do insert_source(_1) end diff --git a/spec/diver_down/web/definition_to_dot_spec.rb b/spec/diver_down/web/definition_to_dot_spec.rb index c4667b2..15dfc46 100644 --- a/spec/diver_down/web/definition_to_dot_spec.rb +++ b/spec/diver_down/web/definition_to_dot_spec.rb @@ -5,15 +5,7 @@ describe '#to_s' do def build_definition(title: 'title', sources: []) definition_sources = sources.map do |source| - dependencies = (source[:dependencies] || []).map do |dependency| - DiverDown::Definition::Dependency.new(**dependency) - end - - modules = (source[:modules] || []).map do |mod| - DiverDown::Definition::Modulee.new(**mod) - end - - DiverDown::Definition::Source.new(**source, dependencies:, modules:) + DiverDown::Definition::Source.from_hash(source) end DiverDown::Definition.new(title:, sources: definition_sources) @@ -156,6 +148,23 @@ def build_definition(title: 'title', sources: []) } DOT end + + it 'returns concentrate digraph if concentrate = true' do + definition = build_definition( + sources: [ + { + source_name: 'a.rb', + }, + ] + ) + + expect(described_class.new(definition, concentrate: true).to_s).to eq(<<~DOT) + strict digraph "title" { + concentrate=true + "a.rb" [label="a.rb"] + } + DOT + end end end end From 660d7c53f8235c20178cea999cba6e8bf30e8934 Mon Sep 17 00:00:00 2001 From: alpaca-tc Date: Thu, 4 Apr 2024 00:06:22 +0900 Subject: [PATCH 6/8] Render one arrow at between modules if compound=1 --- lib/diver_down/web/definition_to_dot.rb | 30 +++++++++++++------ spec/diver_down/web/definition_to_dot_spec.rb | 9 ++++-- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/lib/diver_down/web/definition_to_dot.rb b/lib/diver_down/web/definition_to_dot.rb index 0e87b31..cb443c0 100644 --- a/lib/diver_down/web/definition_to_dot.rb +++ b/lib/diver_down/web/definition_to_dot.rb @@ -88,6 +88,7 @@ def initialize(definition, compound: false, concentrate: false) @io = DiverDown::IndentedStringIo.new @indent = 0 @compound = compound + @compound_map = Hash.new { |h, k| h[k] = Set.new } # { ltail => Set } @concentrate = concentrate end @@ -122,14 +123,25 @@ def insert_source(source) source.dependencies.each do attributes = {} - - if @compound - ltail = module_label(*source.modules) - lhead = module_label(*definition.source(_1.source_name).modules) - - attributes.merge!(ltail:, lhead:) + ltail = module_label(*source.modules) + lhead = module_label(*definition.source(_1.source_name).modules) + skip_rendering = false + + if @compound && (ltail || lhead) + # Rendering of dependencies between modules is done only once + between_modules = ltail != lhead + skip_rendering ||= @compound_map[ltail].include?(lhead) if between_modules + @compound_map[ltail].add(lhead) + + attributes.merge!( + ltail:, + lhead:, + minlen: (between_modules ? 3 : nil) # Between modules is prominently distanced + ) end + next if skip_rendering + io.write(%("#{source.source_name}" -> "#{_1.source_name}")) io.write(%( #{build_attributes(**attributes)}), indent: false) unless attributes.empty? io.write("\n") @@ -176,10 +188,10 @@ def insert_modules(source) # rubocop:disable Lint/UnderscorePrefixedVariableName # attrsの参考 https://qiita.com/rubytomato@github/items/51779135bc4b77c8c20d def build_attributes(_wrap: '[]', **attrs) - attrs_str = attrs.filter_map { %(#{_1}="#{_2}") if _2 }.join(ATTRIBUTE_DELIMITER) - attrs.merge!(label: 'a-b', headlabel: 'head', taillabel: 'tail') + attrs = attrs.reject { _2.nil? || _2 == '' } + return if attrs.empty? - return if attrs_str.empty? + attrs_str = attrs.map { %(#{_1}="#{_2}") }.join(ATTRIBUTE_DELIMITER) if _wrap "#{_wrap[0]}#{attrs_str}#{_wrap[1]}" diff --git a/spec/diver_down/web/definition_to_dot_spec.rb b/spec/diver_down/web/definition_to_dot_spec.rb index 15dfc46..e9bc3fc 100644 --- a/spec/diver_down/web/definition_to_dot_spec.rb +++ b/spec/diver_down/web/definition_to_dot_spec.rb @@ -54,6 +54,9 @@ def build_definition(title: 'title', sources: []) }, ], }, + { + source_name: 'b.rb', + } ] ) @@ -61,6 +64,7 @@ def build_definition(title: 'title', sources: []) strict digraph "title" { "a.rb" [label="a.rb"] "a.rb" -> "b.rb" + "b.rb" [label="b.rb"] } DOT end @@ -79,7 +83,7 @@ def build_definition(title: 'title', sources: []) module_name: 'B', }, ], - }, + } ] ) @@ -137,8 +141,7 @@ def build_definition(title: 'title', sources: []) subgraph "cluster_A" { label="A" "a.rb" [label="a.rb"] } - "a.rb" -> "b.rb" [ltail="cluster_A" lhead="cluster_B"] - "a.rb" -> "c.rb" [ltail="cluster_A" lhead="cluster_B"] + "a.rb" -> "b.rb" [ltail="cluster_A" lhead="cluster_B" minlen="3"] subgraph "cluster_B" { label="B" "b.rb" [label="b.rb"] } From af84389ab7c2ef36bc41cba32fc12269ebc18578 Mon Sep 17 00:00:00 2001 From: alpaca-tc Date: Thu, 4 Apr 2024 00:09:46 +0900 Subject: [PATCH 7/8] Fix ci --- .github/workflows/ci_frontend.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci_frontend.yml b/.github/workflows/ci_frontend.yml index 76f2387..3f937f7 100644 --- a/.github/workflows/ci_frontend.yml +++ b/.github/workflows/ci_frontend.yml @@ -14,6 +14,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Setup pnpm + uses: pnpm/action-setup@v2 - name: Set up frontend uses: actions/setup-node@v4 with: @@ -26,6 +28,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Setup pnpm + uses: pnpm/action-setup@v2 - name: Set up frontend uses: actions/setup-node@v4 with: From af584fe3368093e13f33490d224a8682ee9473c3 Mon Sep 17 00:00:00 2001 From: alpaca-tc Date: Thu, 4 Apr 2024 00:09:51 +0900 Subject: [PATCH 8/8] pnpm run format --- frontend/pages/Home/Show.tsx | 17 ++++++++++++++--- .../ConfigureGraphOptionsDialog.tsx | 15 ++++++--------- .../Home/components/DefinitionGraph/index.ts | 2 +- spec/diver_down/web/definition_to_dot_spec.rb | 4 ++-- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/frontend/pages/Home/Show.tsx b/frontend/pages/Home/Show.tsx index 931103d..e4b0fe9 100644 --- a/frontend/pages/Home/Show.tsx +++ b/frontend/pages/Home/Show.tsx @@ -14,8 +14,15 @@ import { DefinitionSources } from './components/DefinitionSources' export const Show: React.FC = () => { const [selectedDefinitionIds, setSelectedDefinitionIds] = useBitIdHash() - const [graphOptions, setGraphOptions] = useLocalStorage('HomeShow-GraphOptions', { compound: false, concentrate: false }) - const { data: combinedDefinition, isLoading } = useCombinedDefinition(selectedDefinitionIds, graphOptions.compound, graphOptions.concentrate) + const [graphOptions, setGraphOptions] = useLocalStorage('HomeShow-GraphOptions', { + compound: false, + concentrate: false, + }) + const { data: combinedDefinition, isLoading } = useCombinedDefinition( + selectedDefinitionIds, + graphOptions.compound, + graphOptions.concentrate, + ) return ( @@ -34,7 +41,11 @@ export const Show: React.FC = () => { ) : ( - + )} diff --git a/frontend/pages/Home/components/DefinitionGraph/ConfigureGraphOptionsDialog.tsx b/frontend/pages/Home/components/DefinitionGraph/ConfigureGraphOptionsDialog.tsx index 30b7c14..08364b1 100644 --- a/frontend/pages/Home/components/DefinitionGraph/ConfigureGraphOptionsDialog.tsx +++ b/frontend/pages/Home/components/DefinitionGraph/ConfigureGraphOptionsDialog.tsx @@ -16,14 +16,8 @@ type Props = { setGraphOptions: React.Dispatch> } -export const ConfigureViewOptionsDialog: React.FC = ({ - isOpen, - onClickClose, - graphOptions, - setGraphOptions, -}) => { - const [temporaryViewOptions, setTemporaryViewOptions] = - useState(graphOptions) +export const ConfigureViewOptionsDialog: React.FC = ({ isOpen, onClickClose, graphOptions, setGraphOptions }) => { + const [temporaryViewOptions, setTemporaryViewOptions] = useState(graphOptions) const handleDialogClose = () => { onClickClose() @@ -73,7 +67,10 @@ export const ConfigureViewOptionsDialog: React.FC = ({ - + diff --git a/frontend/pages/Home/components/DefinitionGraph/index.ts b/frontend/pages/Home/components/DefinitionGraph/index.ts index da6b236..9eda01b 100644 --- a/frontend/pages/Home/components/DefinitionGraph/index.ts +++ b/frontend/pages/Home/components/DefinitionGraph/index.ts @@ -1,2 +1,2 @@ export { DefinitionGraph } from './DefinitionGraph' -export type { GraphOptions } from "./ConfigureGraphOptionsDialog" +export type { GraphOptions } from './ConfigureGraphOptionsDialog' diff --git a/spec/diver_down/web/definition_to_dot_spec.rb b/spec/diver_down/web/definition_to_dot_spec.rb index e9bc3fc..12706ef 100644 --- a/spec/diver_down/web/definition_to_dot_spec.rb +++ b/spec/diver_down/web/definition_to_dot_spec.rb @@ -56,7 +56,7 @@ def build_definition(title: 'title', sources: []) }, { source_name: 'b.rb', - } + }, ] ) @@ -83,7 +83,7 @@ def build_definition(title: 'title', sources: []) module_name: 'B', }, ], - } + }, ] )