diff --git a/frontend/models/source.ts b/frontend/models/source.ts index 31828ca..b67a8a3 100644 --- a/frontend/models/source.ts +++ b/frontend/models/source.ts @@ -3,6 +3,7 @@ import { Module } from './module' export type Source = { sourceName: string + modules: Module[] } type RelatedDefinition = { @@ -21,3 +22,43 @@ export type SpecificSource = { relatedDefinitions: RelatedDefinition[] reverseDependencies: ReverseDependency[] } + +export type Sources = { + sources: Source[] + classifiedSourcesCount: number +} + +export const sortSources = (sources: Source[], key: 'sourceName' | 'modules', sort: 'none' | 'asc' | 'desc'): Source[] => { + if (sort === 'none') { + return sources + } + + let sorted = [...sources] + + const ascString = (a: string, b: string) => { + if (a > b) return 1 + if (a < b) return -1 + return 0 + } + + switch (key) { + case 'sourceName': { + sorted = sorted.sort((a, b) => ascString(a.sourceName, b.sourceName)) + break + } + case 'modules': { + sorted = sorted.sort((a, b) => + ascString( + a.modules.map((module) => module.moduleName).join('-'), + b.modules.map((module) => module.moduleName).join('-'), + ), + ) + } + } + + if (sort === 'desc') { + sorted = sorted.reverse() + } + + return sorted +} diff --git a/frontend/pages/DefinitionList/components/DefinitionSources/DefinitionSources.tsx b/frontend/pages/DefinitionList/components/DefinitionSources/DefinitionSources.tsx index 36d4b89..0071e02 100644 --- a/frontend/pages/DefinitionList/components/DefinitionSources/DefinitionSources.tsx +++ b/frontend/pages/DefinitionList/components/DefinitionSources/DefinitionSources.tsx @@ -8,6 +8,7 @@ import { color } from '@/constants/theme' import { CombinedDefinition } from '@/models/combinedDefinition' import { SourceModulesComboBox } from '../SourceModulesComboBox' +import { sortSources } from '@/models/source' type Props = { combinedDefinition: CombinedDefinition @@ -44,38 +45,7 @@ export const DefinitionSources: FC = ({ combinedDefinition, mutateCombine ) const sources: CombinedDefinition['sources'] = useMemo(() => { - let sorted = [...combinedDefinition.sources] - - if (sortState.sort === 'none') { - return sorted - } - - const ascString = (a: string, b: string) => { - if (a > b) return 1 - if (a < b) return -1 - return 0 - } - - switch (sortState.key) { - case 'sourceName': { - sorted = sorted.sort((a, b) => ascString(a.sourceName, b.sourceName)) - break - } - case 'modules': { - sorted = sorted.sort((a, b) => - ascString( - a.modules.map((module) => module.moduleName).join('-'), - b.modules.map((module) => module.moduleName).join('-'), - ), - ) - } - } - - if (sortState.sort === 'desc') { - sorted = sorted.reverse() - } - - return sorted + return sortSources(combinedDefinition.sources, sortState.key, sortState.sort) }, [combinedDefinition.sources, sortState]) return ( diff --git a/frontend/pages/Sources/List.tsx b/frontend/pages/Sources/List.tsx index 3205bee..12b31dd 100644 --- a/frontend/pages/Sources/List.tsx +++ b/frontend/pages/Sources/List.tsx @@ -1,4 +1,4 @@ -import { FC } from 'react' +import { FC, useCallback, useMemo, useState } from 'react' import styled from 'styled-components' import { Link } from '@/components/Link' @@ -7,20 +7,60 @@ import { EmptyTableBody, Heading, Section, Stack, Table, Td, Text, Th } from '@/ import { path } from '@/constants/path' import { spacing } from '@/constants/theme' import { useSources } from '@/repositories/sourceRepository' +import { Sources, sortSources } from '@/models/source' + +const sortTypes = ['asc', 'desc', 'none'] as const + +type SortTypes = (typeof sortTypes)[number] + +type SortState = { + key: 'sourceName' | 'modules' + sort: SortTypes +} export const List: FC = () => { - const { sources, isLoading } = useSources() + const { data, isLoading } = useSources() + const [sortState, setSortState] = useState({ key: 'sourceName', sort: 'asc' }) + + const setNextSortType = useCallback( + (key: SortState['key']) => { + setSortState((prev) => { + if (prev.key === key) { + return { + key, + sort: sortTypes[(sortTypes.indexOf(prev.sort) + 1) % sortTypes.length], + } + } else { + return { key, sort: 'asc' } + } + }) + }, + [setSortState], + ) + + const sources: Sources['sources'] = useMemo(() => { + if (!data) { + return [] + } + + return sortSources(data.sources, sortState.key, sortState.sort) + }, [data?.sources, sortState]) return ( - Sources + Sources {data ? `(classified: ${Math.round(data.classifiedSourcesCount / data.sources.length * 100)}% ${data.classifiedSourcesCount} / ${data.sources.length})` : null}
- + + {sources && sources.length > 0 ? ( @@ -30,6 +70,15 @@ export const List: FC = () => { + ))} diff --git a/frontend/repositories/sourceRepository.ts b/frontend/repositories/sourceRepository.ts index cec6285..70817e5 100644 --- a/frontend/repositories/sourceRepository.ts +++ b/frontend/repositories/sourceRepository.ts @@ -1,21 +1,33 @@ import useSWR from 'swr' import { path } from '@/constants/path' -import { SpecificSource } from '@/models/source' +import { Sources, SpecificSource } from '@/models/source' import { get } from './httpRequest' type SourcesReponse = { - sources: Array<{ source_name: string }> + sources: Array<{ + source_name: string + modules: Array<{ + module_name: string + }> + }> + classified_sources_count: number } export const useSources = () => { - const { data, isLoading } = useSWR(path.api.sources.index(), async () => { + const { data, isLoading } = useSWR(path.api.sources.index(), async () => { const response = await get(path.api.sources.index()) - return response.sources.map((source) => ({ sourceName: source.source_name })) + return { + sources: response.sources.map((source) => ({ + sourceName: source.source_name, + modules: source.modules.map((module) => ({ moduleName: module.module_name })), + })), + classifiedSourcesCount: response.classified_sources_count + } }) - return { sources: data, isLoading } + return { data, isLoading } } type SpecificSourceResponse = { diff --git a/lib/diver_down/web/action.rb b/lib/diver_down/web/action.rb index 9717006..afbc487 100644 --- a/lib/diver_down/web/action.rb +++ b/lib/diver_down/web/action.rb @@ -33,12 +33,18 @@ def sources end # rubocop:enable Style/HashEachMethods + classified_sources_count = source_names.count { @module_store.include?(_1) } + json( sources: source_names.sort.map do |source_name| { source_name:, + modules: @module_store.get(source_name).map do |module_name| + { module_name: } + end, } - end + end, + classified_sources_count: ) end diff --git a/spec/diver_down/web_spec.rb b/spec/diver_down/web_spec.rb index 6b093ea..629167e 100644 --- a/spec/diver_down/web_spec.rb +++ b/spec/diver_down/web_spec.rb @@ -294,6 +294,7 @@ def assert_definition_group(definition_group, expected_ids) expect(last_response.status).to eq(200) expect(JSON.parse(last_response.body)).to eq({ 'sources' => [], + 'classified_sources_count' => 0, }) end @@ -304,10 +305,18 @@ def assert_definition_group(definition_group, expected_ids) DiverDown::Definition::Source.new( source_name: 'a.rb' ), + DiverDown::Definition::Source.new( + source_name: 'b.rb' + ), + DiverDown::Definition::Source.new( + source_name: 'c.rb' + ), ] ) store.set(definition) + module_store.set('a.rb', ['A']) + get '/api/sources.json' expect(last_response.status).to eq(200) @@ -315,8 +324,18 @@ def assert_definition_group(definition_group, expected_ids) 'sources' => [ { 'source_name' => 'a.rb', + 'modules' => [{ 'module_name' => 'A' }], + }, + { + 'source_name' => 'b.rb', + 'modules' => [], + }, + { + 'source_name' => 'c.rb', + 'modules' => [], }, ], + 'classified_sources_count' => 1, }) end end
Source name setNextSortType('sourceName')}> + Source name + setNextSortType('modules')}> + Modules +
{source.sourceName} + {source.modules.map((module, index) => ( + + mod.moduleName))}> + {module.moduleName} + + + ))} +