Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added - 2025-12-25

#### TableField and TableQuestion Support (#114)

**New Features:**
- Added support for `TableField` type in dataset field configuration
- Table field type now available in field type dropdown
- Users can map JSON table types to TableField
- Added `isTableType` getter for type checking

- Added support for `TableQuestion` type in dataset question configuration
- Table question type now available in question type dropdown
- Dynamic column management (add/remove columns)
- Each column configurable with name, title, and description
- Default initialization with 2 sample columns
- Validation ensures at least one column is defined

**Components:**
- New `DatasetConfigurationTableQuestion.vue` component for table question UI
- Column list with inline editing
- Add/remove column buttons
- Input validation with error display
- Focus event handling for better UX

**Backend Changes:**
- `FieldCreation.ts`: Added table field type support
- Added to `availableFieldTypes` array
- Updated `FieldCreationTypes` type
- Added `isTableType` getter method

- `QuestionCreation.ts`: Added table question type support
- Added to `availableQuestionTypes` array
- Imported and integrated `TableQuestionAnswer`
- Added `isTableType` getter method
- Implemented `createInitialAnswers()` for table questions
- Added validation requiring at least one column

- `Subset.ts`: Added default settings initialization
- Auto-creates 2 default columns when table question added
- Sets `use_table: true` flag

**UI Updates:**
- `DatasetConfigurationQuestion.vue`: Added conditional rendering for table questions
- Added i18n translations for table question UI elements

**Tests:**
- Added unit tests for table question initialization
- Added unit tests for table question validation
- Added comprehensive Vue component tests for `DatasetConfigurationTableQuestion`
- Component rendering tests
- Column management tests (add/remove/update)
- Validation behavior tests
- Event emission tests

**Files Modified:**
- `extralit-frontend/v1/domain/entities/hub/FieldCreation.ts`
- `extralit-frontend/v1/domain/entities/hub/QuestionCreation.ts`
- `extralit-frontend/v1/domain/entities/hub/Subset.ts`
- `extralit-frontend/components/features/dataset-creation/configuration/questions/DatasetConfigurationQuestion.vue`
- `extralit-frontend/translation/en.js`
- `extralit-frontend/v1/domain/entities/hub/DatasetCreation.test.ts`

**Files Added:**
- `extralit-frontend/components/features/dataset-creation/configuration/questions/DatasetConfigurationTableQuestion.vue`
- `extralit-frontend/components/features/dataset-creation/configuration/questions/DatasetConfigurationTableQuestion.test.ts`

**Impact:**
- Users can now create and configure table fields in dataset schemas
- Users can now create table questions for table annotation workflows
- No breaking changes to existing question or field types
- Follows existing architectural patterns for extensibility

**Related Issue:** #114
5 changes: 5 additions & 0 deletions docker-compose.override.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
services:
elasticsearch:
environment:
- ES_JAVA_OPTS=-Xms512m -Xmx512m
- CLI_JAVA_OPTS=
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<transition-group class="config-form__draggable-area-wrapper" type="transition" :css="false">
<DatasetConfigurationField
v-for="field in dataset.selectedSubset.fields.filter((f) => f.name !== dataset.mappings.external_id)"
:key="field.name" :field="field" :available-types="availableFieldTypes.filter((a) => a.value === 'no mapping' || a.value === field.originalType.value)
:key="field.name" :field="field" :available-types="availableFieldTypes.filter((a) => a.value === 'no mapping' || a.value === field.originalType.value || a.value === 'table')
" @is-focused="isFocused = $event" />
</transition-group>
</draggable>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
:available-types="availableTypes"
@is-focused="$emit('is-focused', $event)"
>
<DatasetConfigurationTableField
v-if="field.type.isTableType"
:field="field"
@is-focused="$emit('is-focused', $event)"
/>
</DatasetConfigurationCard>
</template>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { shallowMount } from "@vue/test-utils";
import DatasetConfigurationTableField from "./DatasetConfigurationTableField.vue";
import { FieldCreation } from "~/v1/domain/entities/hub/FieldCreation";

describe("DatasetConfigurationTableField", () => {
let mockField: FieldCreation;

beforeEach(() => {
mockField = FieldCreation.from("table_field", "table", "object");
});

describe("rendering", () => {
it("renders without crashing", () => {
const wrapper = shallowMount(DatasetConfigurationTableField, {
propsData: {
field: mockField,
},
mocks: {
$t: (key: string) => key,
},
});

expect(wrapper.exists()).toBe(true);
expect(wrapper.find(".table-field-config").exists()).toBe(true);
});

it("renders table preview section", () => {
const wrapper = shallowMount(DatasetConfigurationTableField, {
propsData: {
field: mockField,
},
mocks: {
$t: (key: string) => key,
},
});

expect(wrapper.find(".table-field-config__preview").exists()).toBe(true);
});

it("renders preview header with columns", () => {
const wrapper = shallowMount(DatasetConfigurationTableField, {
propsData: {
field: mockField,
},
mocks: {
$t: (key: string) => key,
},
});

const headerCells = wrapper.findAll(
".table-field-config__preview-cell--header"
);
expect(headerCells.length).toBe(3);
});

it("renders preview rows", () => {
const wrapper = shallowMount(DatasetConfigurationTableField, {
propsData: {
field: mockField,
},
mocks: {
$t: (key: string) => key,
},
});

const rows = wrapper.findAll(".table-field-config__preview-row");
expect(rows.length).toBe(2);
});

it("renders help text", () => {
const wrapper = shallowMount(DatasetConfigurationTableField, {
propsData: {
field: mockField,
},
mocks: {
$t: (key: string) => key,
},
});

const helpLabel = wrapper.find(".table-field-config__help");
expect(helpLabel.exists()).toBe(true);
expect(helpLabel.text()).toBe("datasetCreation.fields.table.helpText");
});

it("renders placeholder cells in preview rows", () => {
const wrapper = shallowMount(DatasetConfigurationTableField, {
propsData: {
field: mockField,
},
mocks: {
$t: (key: string) => key,
},
});

const placeholders = wrapper.findAll(
".table-field-config__preview-placeholder"
);
// 2 rows × 3 columns = 6 placeholders
expect(placeholders.length).toBe(6);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<template>
<div class="table-field-config">
<div class="table-field-config__preview">
<div class="table-field-config__preview-header">
<div
v-for="col in previewColumns"
:key="col"
class="table-field-config__preview-cell table-field-config__preview-cell--header"
>
{{ col }}
</div>
</div>
<div

Check failure on line 13 in extralit-frontend/components/features/dataset-creation/configuration/fields/DatasetConfigurationTableField.vue

View workflow job for this annotation

GitHub Actions / Build extralit-frontend

Replace `⏎········v-for="row·in·previewRows"⏎········:key="row"⏎········class="table-field-config__preview-row"⏎······` with `·v-for="row·in·previewRows"·:key="row"·class="table-field-config__preview-row"`
v-for="row in previewRows"
:key="row"
class="table-field-config__preview-row"
>
<div

Check failure on line 18 in extralit-frontend/components/features/dataset-creation/configuration/fields/DatasetConfigurationTableField.vue

View workflow job for this annotation

GitHub Actions / Build extralit-frontend

Replace `⏎··········v-for="col·in·previewColumns"⏎··········:key="col"⏎··········class="table-field-config__preview-cell"⏎········` with `·v-for="col·in·previewColumns"·:key="col"·class="table-field-config__preview-cell"`
v-for="col in previewColumns"
:key="col"
class="table-field-config__preview-cell"
>
<span class="table-field-config__preview-placeholder" />
</div>
</div>
</div>
<label

Check failure on line 27 in extralit-frontend/components/features/dataset-creation/configuration/fields/DatasetConfigurationTableField.vue

View workflow job for this annotation

GitHub Actions / Build extralit-frontend

Replace `⏎······class="table-field-config__help"⏎······v-text="$t('datasetCreation.fields.table.helpText')"⏎···` with `·class="table-field-config__help"·v-text="$t('datasetCreation.fields.table.helpText')"`
class="table-field-config__help"
v-text="$t('datasetCreation.fields.table.helpText')"
/>
</div>
</template>

<script>
export default {
props: {
field: {
type: Object,
required: true,
},
},
data() {
return {
previewColumns: ["Column A", "Column B", "Column C"],
previewRows: [1, 2],
};
},
};
</script>

<style lang="scss" scoped>
.table-field-config {
display: flex;
flex-direction: column;
gap: $base-space;
&__preview {
border: 1px solid var(--bg-opacity-10);
border-radius: $border-radius;
overflow: hidden;
}
&__preview-header {
display: flex;
background: var(--bg-accent-grey-2);
border-bottom: 1px solid var(--bg-opacity-10);
}
&__preview-row {
display: flex;
border-bottom: 1px solid var(--bg-opacity-6);
&:last-child {
border-bottom: none;
}
}
&__preview-cell {
flex: 1;
padding: calc($base-space / 2) $base-space;
@include font-size(11px);
color: var(--fg-tertiary);
border-right: 1px solid var(--bg-opacity-6);
min-height: calc($base-space * 3.5);
display: flex;
align-items: center;
&:last-child {
border-right: none;
}
&--header {
font-weight: 600;
color: var(--fg-secondary);
@include font-size(11px);
}
}
&__preview-placeholder {
display: block;
height: 8px;
width: 70%;
background: var(--bg-opacity-6);
border-radius: $border-radius;
}
&__help {
color: var(--fg-secondary);
@include font-size(12px);
}
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
v-model="question.settings.options"
@is-focused="$emit('is-focused', $event)"
/>
<DatasetConfigurationTableQuestion
v-else-if="question.settings.type.isTableType"
:question="question"
@is-focused="$emit('is-focused', $event)"
/>
</template>
<span class="separator"></span>
<DatasetConfigurationColumnSelector
Expand Down
Loading
Loading