Skip to content

Commit

Permalink
[FEATURE-BRANCH] ImageField: add support to new fields of type image (#…
Browse files Browse the repository at this point in the history
…5279)

# Description

This PR is the first one related with the addition of a new field of
type `image` including the following changes:
* Changes in the backend to support a new `image` field type supporting
URLs and Data URLs.
* Changes in the frontend rendering this new `image` field for records.
* Changes in the SDK to define datasets with `image` fields and create
records specifying images.
* Changes in the documentation with examples on how to use the SDK with
this new type of field.

This new type will have the following characteristics:
* A new filed type `image` is supported (without additional settings).
* Records created with fields of type `image` are validated to have
correct Web URLs or data URLs like:
  *  `https://argilla.io/image.jpeg`:
     * `HTTPS` schema is present
     * A host and a path is present.
     * The URL is not longer than `2038` characters.
  * `http://argilla.io/image.jpeg`:
     * `HTTP` schema is present
     * A host and a path is present.
     * The URL is not longer than `2038` characters.
  * `data:image/webp;base64,UklGRhgCAABXRUJQVlA4WA`:
     * `data` schema is present
     * A path is present.
     * The Data URL is not longer than `5` million characters.
     * A valid MIME type is present.

Closes #5276

**Type of change**

- New feature (non-breaking change which adds functionality)

**How Has This Been Tested**

- [x] Adding new tests to our suite checking this new field.

**Checklist**

- I added relevant documentation
- I followed the style guidelines of this project
- I did a self-review of my code
- I made corresponding changes to the documentation
- I confirm My changes generate no new warnings
- I have added tests that prove my fix is effective or that my feature
works
- I have added relevant notes to the CHANGELOG.md file (See
https://keepachangelog.com/)

---------

Co-authored-by: Paco Aranda <[email protected]>
Co-authored-by: burtenshaw <[email protected]>
Co-authored-by: David Berenstein <[email protected]>
Co-authored-by: Ben Burtenshaw <[email protected]>
Co-authored-by: Damián Pumar <[email protected]>
Co-authored-by: Leire Aguirre <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Francisco Aranda <[email protected]>
  • Loading branch information
9 people authored Sep 3, 2024
1 parent 745e57b commit 0f05270
Show file tree
Hide file tree
Showing 46 changed files with 2,903 additions and 153 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
<template>
<div class="fields">
<div v-for="{ id, name, title, content, settings } in fields" :key="id">
<div
v-for="{
id,
name,
title,
content,
settings,
isTextType,
isImageType,
} in fields"
:class="[isImageType ? 'fields__container--image' : '']"
:key="id"
>
<SpanAnnotationTextField
v-if="hasSpanQuestion(name)"
:id="`${id}-${record.id}-span-field`"
Expand All @@ -10,14 +22,15 @@
:spanQuestion="getSpanQuestion(name)"
:searchText="recordCriteria.committed.searchText.value.text"
/>
<TextFieldComponent
v-else
<TextField
v-else-if="isTextType"
:name="name"
:title="title"
:fieldText="content"
:useMarkdown="settings.use_markdown"
:searchText="recordCriteria.committed.searchText.value.text"
/>
<ImageField v-else :name="name" :title="title" :content="content" />
</div>
</div>
</template>
Expand Down Expand Up @@ -60,5 +73,12 @@ export default {
min-width: 0;
height: 100%;
min-height: 0;
&__container {
&--image {
overflow-x: auto;
overflow-y: auto;
}
}
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<template>
<div class="image_field" :key="content">
<span class="image_field_title" v-text="title" />
<template v-if="!hasError">
<BaseSpinner v-if="!isLoaded" />
<img
v-show="isLoaded"
:src="content"
@error="handleError()"
@load="onLoad()"
/>
</template>
<div v-else class="image_field_placeholder">
<img src="images/img-placeholder.svg" />
<p v-text="$t('couldNotLoadImage')" />
</div>
</div>
</template>

<script>
export default {
props: {
name: {
type: String,
required: true,
},
title: {
type: String,
required: true,
},
content: {
type: String,
required: true,
},
},
data() {
return {
hasError: false,
isLoaded: false,
};
},
methods: {
handleError() {
this.hasError = true;
},
onLoad() {
this.isLoaded = true;
},
},
};
</script>

<style lang="scss" scoped>
.image_field {
$this: &;
display: flex;
flex-direction: column;
min-height: 100%;
min-width: 100%;
height: fit-content;
width: fit-content;
gap: $base-space * 2;
padding: 2 * $base-space;
background: palette(grey, 800);
border-radius: $border-radius-m;
&_placeholder {
display: flex;
flex-direction: column;
width: 300px;
max-width: 100%;
margin: auto;
align-items: center;
color: $black-37;
}
img {
max-width: fit-content;
max-height: fit-content;
height: fit-content;
width: fit-content;
}
&_title {
word-break: break-word;
width: calc(100% - 30px);
}
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
import { useTextFieldViewModel } from "./useTextFieldViewModel";
export default {
name: "TextFieldComponent",
props: {
name: {
type: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
>
<div class="settings__edition-form__name">
<h4 class="--body1 --medium" v-text="field.name" />
<BaseBadge class="--capitalized" :text="`${$t(field.type)}`" />
</div>

<Validation
Expand All @@ -20,6 +21,7 @@
</Validation>

<BaseSwitch
v-if="field.isTextType"
class="settings__edition-form__switch"
v-model="field.settings.use_markdown"
>{{ $t("useMarkdown") }}</BaseSwitch
Expand Down Expand Up @@ -103,6 +105,9 @@ export default {
h4 {
margin: 0;
}
.badge {
margin-inline: 0 auto;
}
}
&__group {
Expand Down
4 changes: 4 additions & 0 deletions argilla-frontend/static/images/img-placeholder.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions argilla-frontend/translation/de.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export default {
label_selection: "Label",
span: "Span",
text: "Text",
image: "Bild",
rating: "Bewertung",
minimize: "Minimieren",
select: "Auswählen",
Expand Down
2 changes: 2 additions & 0 deletions argilla-frontend/translation/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export default {
label_selection: "Label",
span: "Span",
text: "Text",
image: "Image",
rating: "Rating",
minimize: "Minimize",
select: "Select",
Expand Down Expand Up @@ -38,6 +39,7 @@ export default {
taskDistributionTooltip:
"A task is complete when all records have the \nminimum number of submitted responses",
noAnnotationGuidelines: "This dataset has no annotation guidelines",
couldNotLoadImage: "Could not load image",
breadcrumbs: {
home: "Home",
datasetSettings: "settings",
Expand Down
16 changes: 10 additions & 6 deletions argilla-frontend/v1/domain/entities/field/Field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,27 @@ export class Field {
this.initializeOriginal();
}

public get isTextType() {
return this.fieldType === "text";
get isTextType() {
return this.type === "text";
}

private get fieldType() {
get isImageType() {
return this.type === "image";
}

get type() {
return this.settings?.type?.toLowerCase() ?? null;
}

public get isModified(): boolean {
get isModified(): boolean {
return (
this.title.trim() !== this.original.title ||
this.settings.use_markdown !== this.original.settings.use_markdown
);
}

private MAX_TITLE_LENGTH = 500;
public validate(): Record<"title", string[]> {
validate(): Record<"title", string[]> {
const validations: Record<"title", string[]> = {
title: [],
};
Expand All @@ -45,7 +49,7 @@ export class Field {
return validations;
}

public get isFieldValid(): boolean {
get isFieldValid(): boolean {
return this.validate().title.length === 0;
}

Expand Down
1 change: 1 addition & 0 deletions argilla-server/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ These are the section headers that we use:

### Added

- Added new `image` type dataset field supporting URLs and Data URLs. ([#5279](https://github.com/argilla-io/argilla/pull/5279))
- Added new endpoint `GET /api/v1/datsets/:dataset_id/users/progress` to compute the users progress. ([#5367](https://github.com/argilla-io/argilla/pull/5367))

### Fixed
Expand Down
54 changes: 49 additions & 5 deletions argilla-server/src/argilla_server/api/schemas/v1/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# limitations under the License.

from datetime import datetime
from typing import Annotated, List, Literal, Optional
from typing import Annotated, List, Literal, Optional, Union
from uuid import UUID

from argilla_server.api.schemas.v1.commons import UpdateSchema
Expand Down Expand Up @@ -49,21 +49,65 @@


class TextFieldSettings(BaseModel):
type: Literal[FieldType.text]
use_markdown: bool


class TextFieldSettingsCreate(BaseModel):
type: Literal[FieldType.text]
use_markdown: bool = False


class TextFieldSettingsUpdate(UpdateSchema):
class TextFieldSettingsUpdate(BaseModel):
type: Literal[FieldType.text]
use_markdown: bool


class ImageFieldSettings(BaseModel):
type: Literal[FieldType.image]


class ImageFieldSettingsCreate(BaseModel):
type: Literal[FieldType.image]


class ImageFieldSettingsUpdate(BaseModel):
type: Literal[FieldType.image]


FieldSettings = Annotated[
Union[
TextFieldSettings,
ImageFieldSettings,
],
PydanticField(..., discriminator="type"),
]


FieldSettingsCreate = Annotated[
Union[
TextFieldSettingsCreate,
ImageFieldSettingsCreate,
],
PydanticField(..., discriminator="type"),
]


FieldSettingsUpdate = Annotated[
Union[
TextFieldSettingsUpdate,
ImageFieldSettingsUpdate,
],
PydanticField(..., discriminator="type"),
]


class Field(BaseModel):
id: UUID
name: str
title: str
required: bool
settings: TextFieldSettings
settings: FieldSettings
dataset_id: UUID
inserted_at: datetime
updated_at: datetime
Expand All @@ -80,11 +124,11 @@ class FieldCreate(BaseModel):
name: FieldName
title: FieldTitle
required: Optional[bool]
settings: TextFieldSettings
settings: FieldSettingsCreate


class FieldUpdate(UpdateSchema):
title: Optional[FieldTitle]
settings: Optional[TextFieldSettingsUpdate]
settings: Optional[FieldSettingsUpdate]

__non_nullable_fields__ = {"title", "settings"}
1 change: 1 addition & 0 deletions argilla-server/src/argilla_server/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

class FieldType(str, Enum):
text = "text"
image = "image"


class ResponseStatus(str, Enum):
Expand Down
9 changes: 9 additions & 0 deletions argilla-server/src/argilla_server/models/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from argilla_server.api.schemas.v1.questions import QuestionSettings
from argilla_server.enums import (
DatasetStatus,
FieldType,
MetadataPropertyType,
QuestionType,
RecordStatus,
Expand Down Expand Up @@ -73,6 +74,14 @@ class Field(DatabaseModel):

__table_args__ = (UniqueConstraint("name", "dataset_id", name="field_name_dataset_id_uq"),)

@property
def is_text(self):
return self.settings.get("type") == FieldType.text

@property
def is_image(self):
return self.settings.get("type") == FieldType.image

def __repr__(self):
return (
f"Field(id={str(self.id)!r}, name={self.name!r}, required={self.required!r}, "
Expand Down
Loading

0 comments on commit 0f05270

Please sign in to comment.