Skip to content

Commit 701ab65

Browse files
authored
feat(wikibase-schema-editor): create column data type indicators (#81)
1 parent d6d4b8c commit 701ab65

9 files changed

Lines changed: 636 additions & 75 deletions

File tree

.kiro/specs/wikibase-schema-editor/tasks.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
- Implement ColumnPalette component
4444
- _Requirements: 2.1, 2.2, 2.4_
4545

46-
- [ ] 8. Create column data type indicators
46+
- [x] 8. Create column data type indicators
4747
- Write tests for column data type display and tooltips
4848
- Write tests for sample value display functionality
4949
- Implement column data type indicators and tooltips

frontend/.eslintrc-auto-import.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@
213213
"useCloned": true,
214214
"useColorMode": true,
215215
"useColumnConversion": true,
216+
"useColumnDataTypeIndicators": true,
216217
"useColumnGeneration": true,
217218
"useConfirm": true,
218219
"useConfirmDialog": true,

frontend/auto-imports.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ declare global {
164164
const useCloned: typeof import('@vueuse/core')['useCloned']
165165
const useColorMode: typeof import('@vueuse/core')['useColorMode']
166166
const useColumnConversion: typeof import('./src/composables/useColumnConversion')['useColumnConversion']
167+
const useColumnDataTypeIndicators: typeof import('./src/composables/useColumnDataTypeIndicators')['useColumnDataTypeIndicators']
167168
const useColumnGeneration: typeof import('./src/composables/useColumnGeneration')['useColumnGeneration']
168169
const useConfirm: typeof import('primevue/useconfirm')['useConfirm']
169170
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
@@ -523,6 +524,7 @@ declare module 'vue' {
523524
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
524525
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
525526
readonly useColumnConversion: UnwrapRef<typeof import('./src/composables/useColumnConversion')['useColumnConversion']>
527+
readonly useColumnDataTypeIndicators: UnwrapRef<typeof import('./src/composables/useColumnDataTypeIndicators')['useColumnDataTypeIndicators']>
526528
readonly useColumnGeneration: UnwrapRef<typeof import('./src/composables/useColumnGeneration')['useColumnGeneration']>
527529
readonly useConfirm: UnwrapRef<typeof import('primevue/useconfirm')['useConfirm']>
528530
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>

frontend/src/components/ColumnPalette.vue

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ const projectStore = useProjectStore()
33
const dragDropStore = useDragDropStore()
44
55
const { convertProjectColumnsToColumnInfo } = useColumnConversion()
6+
const {
7+
formatDataTypeDisplayName,
8+
generateColumnTooltip,
9+
getDataTypeIcon,
10+
getDataTypeSeverity,
11+
generateSampleStats,
12+
} = useColumnDataTypeIndicators()
613
714
const columns = computed(() => {
815
return convertProjectColumnsToColumnInfo(projectStore.columns, projectStore.data)
@@ -80,34 +87,43 @@ const formatSampleValues = (sampleValues: string[]) => {
8087
>
8188
<div
8289
class="bg-white border border-surface-300 rounded-lg p-3 shadow-sm hover:shadow-md transition-shadow"
90+
:title="generateColumnTooltip(column)"
8391
>
8492
<div class="flex items-center gap-2 mb-2">
93+
<i
94+
:class="getDataTypeIcon(column.dataType)"
95+
class="text-surface-600 text-sm"
96+
:title="`${formatDataTypeDisplayName(column.dataType)} column`"
97+
></i>
8598
<span class="font-medium text-surface-900">{{ column.name }}</span>
8699
<Chip
87-
:label="column.dataType"
100+
:label="formatDataTypeDisplayName(column.dataType)"
88101
size="small"
89-
severity="secondary"
102+
:severity="getDataTypeSeverity(column.dataType)"
90103
class="text-xs"
104+
:title="`Database type: ${column.dataType}`"
91105
/>
92106
<i
93107
v-if="column.nullable"
94108
class="pi pi-question-circle text-surface-400 text-xs"
95-
title="Nullable column"
109+
title="This column allows null values"
96110
></i>
97111
</div>
98112

99113
<div
100114
v-if="column.sampleValues && column.sampleValues.length > 0"
101115
class="text-xs text-surface-600"
116+
:title="`Sample values: ${column.sampleValues.slice(0, 5).join(', ')}${column.sampleValues.length > 5 ? '...' : ''}`"
102117
>
103118
{{ formatSampleValues(column.sampleValues) }}
104119
</div>
105120

106121
<div
107122
v-if="column.uniqueCount !== undefined"
108123
class="text-xs text-surface-500 mt-1"
124+
:title="`This column has ${column.uniqueCount.toLocaleString()} unique values`"
109125
>
110-
{{ column.uniqueCount }} unique values
126+
{{ column.uniqueCount.toLocaleString() }} unique values
111127
</div>
112128
</div>
113129
</div>

frontend/src/components/__tests__/ColumnPalette.test.ts

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
import { describe, test, expect } from 'bun:test'
22
import type { ColumnInfo } from '@frontend/types/wikibase-schema'
3+
import { useColumnDataTypeIndicators } from '@frontend/composables/useColumnDataTypeIndicators'
4+
5+
/**
6+
* ColumnPalette Component Tests
7+
*
8+
* These tests verify the integration between the ColumnPalette component and the
9+
* useColumnDataTypeIndicators composable
10+
*/
311

412
// Test the component logic without DOM testing since we don't have full test setup
513
describe('ColumnPalette Component Logic', () => {
@@ -192,4 +200,137 @@ describe('ColumnPalette Component Logic', () => {
192200
expect(column.nullable).toBe(true)
193201
})
194202
})
203+
204+
describe('column data type display and tooltips', () => {
205+
const {
206+
getCompatibleWikibaseTypes,
207+
formatDataTypeDisplayName,
208+
generateDataTypeTooltip,
209+
generateColumnTooltip,
210+
} = useColumnDataTypeIndicators()
211+
212+
test('should generate correct tooltip content for data types', () => {
213+
const column: ColumnInfo = {
214+
name: 'test_column',
215+
dataType: 'VARCHAR',
216+
sampleValues: ['sample1', 'sample2', 'sample3'],
217+
nullable: true,
218+
uniqueCount: 150,
219+
}
220+
221+
const tooltip = generateDataTypeTooltip(column)
222+
223+
expect(tooltip).toContain('Data Type: Text (VARCHAR)')
224+
expect(tooltip).toContain('Nullable: Yes')
225+
expect(tooltip).toContain('Unique Values: 150')
226+
expect(tooltip).toContain('Wikibase Compatible: string, url, external-id, monolingualtext')
227+
})
228+
229+
test('should handle tooltip content for columns without unique count', () => {
230+
const column: ColumnInfo = {
231+
name: 'test_column',
232+
dataType: 'INTEGER',
233+
sampleValues: ['1', '2'],
234+
nullable: false,
235+
}
236+
237+
const tooltip = generateDataTypeTooltip(column)
238+
239+
expect(tooltip).toContain('Data Type: Number (INTEGER)')
240+
expect(tooltip).not.toContain('Nullable: Yes')
241+
expect(tooltip).not.toContain('Unique Values:')
242+
expect(tooltip).toContain('Wikibase Compatible: quantity')
243+
})
244+
245+
test('should generate data type compatibility information', () => {
246+
expect(getCompatibleWikibaseTypes('VARCHAR')).toEqual([
247+
'string',
248+
'url',
249+
'external-id',
250+
'monolingualtext',
251+
])
252+
expect(getCompatibleWikibaseTypes('INTEGER')).toEqual(['quantity'])
253+
expect(getCompatibleWikibaseTypes('DATE')).toEqual(['time'])
254+
expect(getCompatibleWikibaseTypes('UNKNOWN')).toEqual([])
255+
})
256+
257+
test('should format data type display names', () => {
258+
expect(formatDataTypeDisplayName('VARCHAR')).toBe('Text')
259+
expect(formatDataTypeDisplayName('INTEGER')).toBe('Number')
260+
expect(formatDataTypeDisplayName('DATE')).toBe('Date')
261+
expect(formatDataTypeDisplayName('DATETIME')).toBe('Date/Time')
262+
expect(formatDataTypeDisplayName('BOOLEAN')).toBe('Boolean')
263+
expect(formatDataTypeDisplayName('DECIMAL')).toBe('Decimal')
264+
expect(formatDataTypeDisplayName('UNKNOWN_TYPE')).toBe('UNKNOWN_TYPE')
265+
})
266+
267+
test('should generate comprehensive column tooltip with data type and sample values', () => {
268+
const column: ColumnInfo = {
269+
name: 'test_column',
270+
dataType: 'VARCHAR',
271+
sampleValues: ['sample1', 'sample2', 'sample3'],
272+
nullable: true,
273+
uniqueCount: 150,
274+
}
275+
276+
const tooltip = generateColumnTooltip(column)
277+
278+
expect(tooltip).toContain('Data Type: Text (VARCHAR)')
279+
expect(tooltip).toContain('Nullable: Yes')
280+
expect(tooltip).toContain('Unique Values: 150')
281+
expect(tooltip).toContain('Sample Values:')
282+
expect(tooltip).toContain('sample1, sample2, sample3')
283+
})
284+
})
285+
286+
describe('sample value display functionality', () => {
287+
const { formatSampleValuesForTooltip, generateSampleStats } = useColumnDataTypeIndicators()
288+
289+
test('should format sample values with proper truncation', () => {
290+
const manyValues = ['val1', 'val2', 'val3', 'val4', 'val5', 'val6', 'val7']
291+
const fewValues = ['val1', 'val2', 'val3']
292+
const emptyValues: string[] = []
293+
294+
expect(formatSampleValuesForTooltip(manyValues, 5)).toBe(
295+
'val1, val2, val3, val4, val5, ... (+2 more)',
296+
)
297+
expect(formatSampleValuesForTooltip(fewValues, 5)).toBe('val1, val2, val3')
298+
expect(formatSampleValuesForTooltip(emptyValues)).toBe('No sample data available')
299+
})
300+
301+
test('should handle long sample values with truncation', () => {
302+
const longValues = ['This is a very long sample value that should be truncated for display']
303+
const shortValues = ['Short value']
304+
305+
const longResult = formatSampleValuesForTooltip(longValues)
306+
const shortResult = formatSampleValuesForTooltip(shortValues)
307+
308+
expect(longResult).toContain('...')
309+
expect(shortResult).toBe('Short value')
310+
})
311+
312+
test('should generate sample value statistics', () => {
313+
const valuesWithNulls = ['value1', 'null', 'value2', '']
314+
const valuesWithoutNulls = ['value1', 'value2', 'value3']
315+
const emptyValues: string[] = []
316+
317+
const statsWithNulls = generateSampleStats(valuesWithNulls)
318+
expect(statsWithNulls.isEmpty).toBe(false)
319+
expect(statsWithNulls.count).toBe(4)
320+
expect(statsWithNulls.hasNulls).toBe(true)
321+
expect(statsWithNulls.nullCount).toBe(2)
322+
323+
const statsWithoutNulls = generateSampleStats(valuesWithoutNulls)
324+
expect(statsWithoutNulls.isEmpty).toBe(false)
325+
expect(statsWithoutNulls.count).toBe(3)
326+
expect(statsWithoutNulls.hasNulls).toBe(false)
327+
expect(statsWithoutNulls.nullCount).toBe(0)
328+
329+
const emptyStats = generateSampleStats(emptyValues)
330+
expect(emptyStats.isEmpty).toBe(true)
331+
expect(emptyStats.count).toBe(0)
332+
expect(emptyStats.hasNulls).toBe(false)
333+
expect(emptyStats.nullCount).toBe(0)
334+
})
335+
})
195336
})

0 commit comments

Comments
 (0)