Skip to content

Commit 9844eaf

Browse files
nick-skriabinhlomzikbmartel
authored
feat: DIA-1794: [FE] Home page (+Tailwind and shadcn integration) (#7009)
Co-authored-by: Andrew <hlomzik@gmail.com> Co-authored-by: bmartel <bmartel@users.noreply.github.com>
1 parent 4108f9e commit 9844eaf

File tree

102 files changed

+6004
-496
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

102 files changed

+6004
-496
lines changed

Dockerfile.development

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# syntax=docker/dockerfile:1
2+
ARG NODE_VERSION=18
3+
ARG PYTHON_VERSION=3.12
4+
ARG POETRY_VERSION=2.0.1
5+
ARG VERSION_OVERRIDE
6+
ARG BRANCH_OVERRIDE
7+
8+
################################ Overview
9+
10+
# This Dockerfile builds a Label Studio environment.
11+
# It consists of three main stages:
12+
# 1. "frontend-builder" - Compiles the frontend assets using Node.
13+
# 2. "frontend-version-generator" - Generates version files for frontend sources.
14+
# 3. "venv-builder" - Prepares the virtualenv environment.
15+
# 4. "py-version-generator" - Generates version files for python sources.
16+
# 5. "prod" - Creates the final production image with the Label Studio, Nginx, and other dependencies.
17+
18+
################################ Stage: frontend-builder (build frontend assets)
19+
FROM --platform=${BUILDPLATFORM} node:${NODE_VERSION} AS frontend-builder
20+
WORKDIR /label-studio/web
21+
22+
COPY web .
23+
COPY pyproject.toml ../pyproject.toml
24+
25+
################################ Stage: venv-builder (prepare the virtualenv)
26+
FROM python:${PYTHON_VERSION}-slim AS venv-builder
27+
ARG POETRY_VERSION
28+
29+
ENV PYTHONUNBUFFERED=1 \
30+
PYTHONDONTWRITEBYTECODE=1 \
31+
PIP_NO_CACHE_DIR=off \
32+
PIP_DISABLE_PIP_VERSION_CHECK=on \
33+
PIP_DEFAULT_TIMEOUT=100 \
34+
PIP_CACHE_DIR="/.cache" \
35+
POETRY_CACHE_DIR="/.poetry-cache" \
36+
POETRY_HOME="/opt/poetry" \
37+
POETRY_VIRTUALENVS_IN_PROJECT=true \
38+
PATH="/opt/poetry/bin:$PATH"
39+
40+
ADD https://install.python-poetry.org /tmp/install-poetry.py
41+
RUN python /tmp/install-poetry.py
42+
43+
RUN --mount=type=cache,target="/var/cache/apt",sharing=locked \
44+
--mount=type=cache,target="/var/lib/apt/lists",sharing=locked \
45+
set -eux; \
46+
apt-get update; \
47+
apt-get install --no-install-recommends -y \
48+
build-essential git; \
49+
apt-get autoremove -y
50+
51+
WORKDIR /label-studio
52+
53+
ENV VENV_PATH="/label-studio/.venv"
54+
ENV PATH="$VENV_PATH/bin:$PATH"
55+
56+
## Starting from this line all packages will be installed in $VENV_PATH
57+
58+
# Copy dependency files
59+
COPY pyproject.toml poetry.lock README.md ./
60+
61+
# Install dependencies without dev packages
62+
RUN --mount=type=cache,target=$POETRY_CACHE_DIR,sharing=locked \
63+
poetry check --lock && poetry install --no-root --without test --extras uwsgi
64+
65+
# Install LS
66+
COPY label_studio label_studio
67+
RUN --mount=type=cache,target=$POETRY_CACHE_DIR,sharing=locked \
68+
# `--extras uwsgi` is mandatory here due to poetry bug: https://github.com/python-poetry/poetry/issues/7302
69+
poetry install --only-root --extras uwsgi && \
70+
python3 label_studio/manage.py collectstatic --no-input
71+
72+
################################ Stage: py-version-generator
73+
FROM venv-builder AS py-version-generator
74+
ARG VERSION_OVERRIDE
75+
ARG BRANCH_OVERRIDE
76+
77+
# Create version_.py and ls-version_.py
78+
RUN --mount=type=bind,source=.git,target=./.git \
79+
VERSION_OVERRIDE=${VERSION_OVERRIDE} BRANCH_OVERRIDE=${BRANCH_OVERRIDE} poetry run python label_studio/core/version.py
80+
81+
################################### Stage: prod
82+
FROM python:${PYTHON_VERSION}-slim AS production
83+
84+
ENV LS_DIR=/label-studio \
85+
HOME=/label-studio \
86+
LABEL_STUDIO_BASE_DATA_DIR=/label-studio/data \
87+
OPT_DIR=/opt/heartex/instance-data/etc \
88+
PATH="/label-studio/.venv/bin:$PATH" \
89+
DJANGO_SETTINGS_MODULE=core.settings.label_studio \
90+
PYTHONUNBUFFERED=1 \
91+
PYTHONDONTWRITEBYTECODE=1
92+
93+
WORKDIR $LS_DIR
94+
95+
# install prerequisites for app
96+
RUN --mount=type=cache,target="/var/cache/apt",sharing=locked \
97+
--mount=type=cache,target="/var/lib/apt/lists",sharing=locked \
98+
set -eux; \
99+
apt-get update; \
100+
apt-get upgrade -y; \
101+
apt-get install --no-install-recommends -y libexpat1 \
102+
gnupg2 curl; \
103+
apt-get autoremove -y
104+
105+
# install nginx
106+
RUN --mount=type=cache,target="/var/cache/apt",sharing=locked \
107+
--mount=type=cache,target="/var/lib/apt/lists",sharing=locked \
108+
set -eux; \
109+
curl -sSL https://nginx.org/keys/nginx_signing.key | gpg --dearmor -o /etc/apt/keyrings/nginx-archive-keyring.gpg >/dev/null; \
110+
DEBIAN_VERSION=$(awk -F '=' '/^VERSION_CODENAME=/ {print $2}' /etc/os-release); \
111+
printf "deb [signed-by=/etc/apt/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/debian ${DEBIAN_VERSION} nginx\n" > /etc/apt/sources.list.d/nginx.list; \
112+
printf "Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" > /etc/apt/preferences.d/99nginx; \
113+
apt-get update; \
114+
apt-get install --no-install-recommends -y nginx; \
115+
apt-get autoremove -y
116+
117+
RUN set -eux; \
118+
mkdir -p $LS_DIR $LABEL_STUDIO_BASE_DATA_DIR $OPT_DIR && \
119+
chown -R 1001:0 $LS_DIR $LABEL_STUDIO_BASE_DATA_DIR $OPT_DIR /var/log/nginx /etc/nginx
120+
121+
COPY --chown=1001:0 deploy/default.conf /etc/nginx/nginx.conf
122+
123+
# Copy essential files for installing Label Studio and its dependencies
124+
COPY --chown=1001:0 pyproject.toml .
125+
COPY --chown=1001:0 poetry.lock .
126+
COPY --chown=1001:0 README.md .
127+
COPY --chown=1001:0 LICENSE LICENSE
128+
COPY --chown=1001:0 licenses licenses
129+
COPY --chown=1001:0 deploy deploy
130+
131+
# Copy files from build stages
132+
COPY --chown=1001:0 --from=venv-builder $LS_DIR $LS_DIR
133+
COPY --chown=1001:0 --from=py-version-generator $LS_DIR/label_studio/core/version_.py $LS_DIR/label_studio/core/version_.py
134+
COPY --chown=1001:0 --from=frontend-builder $LS_DIR/web/dist $LS_DIR/web/dist
135+
136+
USER 1001
137+
138+
EXPOSE 8080
139+
140+
ENTRYPOINT ["./deploy/docker-entrypoint.sh"]
141+
CMD ["label-studio"]

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ docker-run-dev:
3333
docker-migrate-dev:
3434
docker compose run app python3 /label-studio/label_studio/manage.py migrate
3535

36+
docker-collectstatic-dev:
37+
docker compose run app python3 /label-studio/label_studio/manage.py collectstatic
38+
3639
# Install modules
3740
frontend-install:
3841
cd web && yarn install --frozen-lockfile;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{% extends 'base.html' %}
2+
{% load i18n %}
3+
{% load static %}

label_studio/core/views.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import pandas as pd
1414
import requests
1515
from core import utils
16-
from core.feature_flags import all_flags, get_feature_file_path
16+
from core.feature_flags import all_flags, flag_set, get_feature_file_path
1717
from core.label_config import generate_time_series_json
1818
from core.utils.common import collect_versions
1919
from core.utils.io import find_file
@@ -27,7 +27,7 @@
2727
HttpResponseServerError,
2828
JsonResponse,
2929
)
30-
from django.shortcuts import redirect, reverse
30+
from django.shortcuts import redirect, render, reverse
3131
from django.template import loader
3232
from django.utils._os import safe_join
3333
from django.views.decorators.csrf import csrf_exempt
@@ -55,7 +55,11 @@ def main(request):
5555
return redirect(reverse('user-login'))
5656

5757
# business mode access
58-
return redirect(reverse('projects:project-index'))
58+
if flag_set('fflag_all_feat_dia_1777_ls_homepage_short', user):
59+
print('redirect to home page')
60+
return render(request, 'home/home.html')
61+
else:
62+
return redirect(reverse('projects:project-index'))
5963

6064
# not authenticated
6165
return redirect(reverse('user-login'))

label_studio/users/templates/users/user_account.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@
173173
<div class="field field--wide">
174174
<label for="example_fetch">Example fetch projects data:</label>
175175
<textarea id="example_fetch" class="example_code ls-textarea" type="text" readonly
176-
style="height: 92px; font-size: 14px">
176+
style="height: 92px; font-size: 14px; padding: 9px 16px">
177177
{% if settings.HOSTNAME %}
178178
curl -X GET {{ settings.HOSTNAME }}/api/projects/ -H 'Authorization: Token {{token}}'
179179
{% else %}

label_studio/users/views.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ def user_signup(request):
4545

4646
# checks if the URL is a safe redirection.
4747
if not next_page or not url_has_allowed_host_and_scheme(url=next_page, allowed_hosts=request.get_host()):
48-
next_page = reverse('projects:project-index')
48+
if flag_set('fflag_all_feat_dia_1777_ls_homepage_short', user):
49+
next_page = reverse('main')
50+
else:
51+
next_page = reverse('projects:project-index')
4952

5053
user_form = forms.UserSignupForm()
5154
organization_form = OrganizationSignupForm()
@@ -105,7 +108,10 @@ def user_login(request):
105108

106109
# checks if the URL is a safe redirection.
107110
if not next_page or not url_has_allowed_host_and_scheme(url=next_page, allowed_hosts=request.get_host()):
108-
next_page = reverse('projects:project-index')
111+
if flag_set('fflag_all_feat_dia_1777_ls_homepage_short', user):
112+
next_page = reverse('main')
113+
else:
114+
next_page = reverse('projects:project-index')
109115

110116
login_form = load_func(settings.USER_LOGIN_FORM)
111117
form = login_form()

web/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ tmp
66

77
# dependencies
88
node_modules
9+
dist
910

1011
# IDEs and editors
1112
/.idea

web/.stylelintrc.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
{
2-
"extends": "stylelint-config-standard-scss",
2+
"extends": ["stylelint-config-tailwindcss/scss", "stylelint-config-standard-scss"],
33
"rules": {
44
"selector-class-pattern": null,
55
"custom-property-pattern": null,
66
"no-descending-specificity": null,
7+
"function-no-unknown": null,
78
"scss/no-global-function-names": null,
9+
"scss/function-no-unknown": null,
810
"selector-pseudo-class-no-unknown": [
911
true,
1012
{

web/apps/labelstudio/src/app/App.jsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,27 @@ import { MultiProvider } from "../providers/MultiProvider";
1212
import { ProjectProvider } from "../providers/ProjectProvider";
1313
import { RoutesProvider } from "../providers/RoutesProvider";
1414
import { DRAFT_GUARD_KEY, DraftGuard, draftGuardCallback } from "../components/DraftGuard/DraftGuard";
15-
import "./App.scss";
1615
import { AsyncPage } from "./AsyncPage/AsyncPage";
1716
import ErrorBoundary from "./ErrorBoundary";
18-
import { RootPage } from "./RootPage";
1917
import { FF_OPTIC_2, FF_UNSAVED_CHANGES, FF_PRODUCT_TOUR, isFF } from "../utils/feature-flags";
2018
import { TourProvider } from "@humansignal/core";
2119
import { ToastProvider, ToastViewport } from "@humansignal/ui";
20+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
21+
import { JotaiProvider, JotaiStore } from "../utils/jotai-store";
2222
import { CurrentUserProvider } from "../providers/CurrentUser";
23+
import { RootPage } from "./RootPage";
24+
import "@humansignal/ui/src/tailwind.css";
25+
import "./App.scss";
2326

2427
const baseURL = new URL(APP_SETTINGS.hostname || location.origin);
2528
export const UNBLOCK_HISTORY_MESSAGE = "UNBLOCK_HISTORY";
29+
const queryClient = new QueryClient({
30+
defaultOptions: {
31+
queries: {
32+
refetchOnWindowFocus: false,
33+
},
34+
},
35+
});
2636

2737
const browserHistory = createBrowserHistory({
2838
basename: baseURL.pathname || "/",
@@ -57,6 +67,8 @@ const App = ({ content }) => {
5767
<Router history={browserHistory}>
5868
<MultiProvider
5969
providers={[
70+
<JotaiProvider key="jotai" store={JotaiStore} />,
71+
<QueryClientProvider key="query" client={queryClient} />,
6072
<AppStoreProvider key="app-store" />,
6173
<ApiProvider key="api" />,
6274
<ConfigProvider key="config" />,

web/apps/labelstudio/src/components/Button/Button.scss

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
border: var(--button-border);
2323
cursor: pointer;
2424
outline: none;
25+
flex-shrink: 0;
2526
display: inline-flex;
2627
text-align: center;
2728
transition: all 100ms ease;
@@ -360,4 +361,4 @@
360361
100% {
361362
background-position: 37px 0;
362363
}
363-
}
364+
}

web/apps/labelstudio/src/components/DescriptionList/DescriptionList.scss

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@
2121
opacity: 0.5;
2222
top: 1px;
2323
position: relative;
24-
}
24+
display: inline;
25+
vertical-align: baseline;
26+
}

web/apps/labelstudio/src/components/Form/Elements/Label/Label.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { clsx } from "clsx";
55
/** @deprecated - needs to be replaced with @humansignal/ui Label - visualizes differently currently */
66
const Label = ({ text, children, required, placement, description, size, large, style, simple, flat, className }) => {
77
const rootClass = cn("label-ls");
8-
const classList = [rootClass];
8+
const classList = [rootClass.toClassName()];
99
const tagName = simple ? "div" : "label";
1010
const mods = {
1111
size,

web/apps/labelstudio/src/components/Menubar/Menubar.jsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { StaticContent } from "../../app/StaticContent/StaticContent";
33
import {
44
IconBook,
55
IconFolder,
6-
IconModel,
76
IconPersonInCircle,
87
IconPin,
98
IconTerminal,
@@ -26,9 +25,10 @@ import { VersionNotifier, VersionProvider } from "../VersionNotifier/VersionNoti
2625
import "./Menubar.scss";
2726
import "./MenuContent.scss";
2827
import "./MenuSidebar.scss";
29-
import { ModelsPage } from "../../pages/Organization/Models/ModelsPage";
30-
import { FF_DIA_835, isFF } from "../../utils/feature-flags";
31-
import { AccountSettingsPage } from "@humansignal/core";
28+
import { FF_HOMEPAGE } from "../../utils/feature-flags";
29+
import { IconHome } from "@humansignal/ui";
30+
import { pages } from "@humansignal/core";
31+
import { isFF } from "../../utils/feature-flags";
3232

3333
export const MenubarContext = createContext();
3434

@@ -135,7 +135,7 @@ export const Menubar = ({ enabled, defaultOpened, defaultPinned, children, onSid
135135
<div className={menubarClass}>
136136
<Dropdown.Trigger dropdown={menuDropdownRef} closeOnClickOutside={!sidebarPinned}>
137137
<div className={`${menubarClass.elem("trigger")} main-menu-trigger`}>
138-
<img src={absoluteURL("/static/icons/logo.svg")} alt="Label Studio Logo" height="22" />
138+
<img src={absoluteURL("/static/icons/logo.svg")} alt="Label Studio Logo" style={{ height: 22 }} />
139139
<Hamburger opened={sidebarOpened} />
140140
</div>
141141
</Dropdown.Trigger>
@@ -151,13 +151,13 @@ export const Menubar = ({ enabled, defaultOpened, defaultPinned, children, onSid
151151
align="right"
152152
content={
153153
<Menu>
154-
<Menu.Item icon={<LsSettings />} label="Account &amp; Settings" href={AccountSettingsPage.path} />
154+
<Menu.Item icon={<LsSettings />} label="Account &amp; Settings" href={pages.AccountSettingsPage.path} />
155155
{/* <Menu.Item label="Dark Mode"/> */}
156156
<Menu.Item icon={<LsDoor />} label="Log Out" href={absoluteURL("/logout")} data-external />
157157
{showNewsletterDot && (
158158
<>
159159
<Menu.Divider />
160-
<Menu.Item className={cn("newsletter-menu-item")} href={AccountSettingsPage.path}>
160+
<Menu.Item className={cn("newsletter-menu-item")} href={pages.AccountSettingsPage.path}>
161161
<span>Please check new notification settings in the Account & Settings page</span>
162162
<span className={cn("newsletter-menu-badge")} />
163163
</Menu.Item>
@@ -186,9 +186,9 @@ export const Menubar = ({ enabled, defaultOpened, defaultPinned, children, onSid
186186
style={{ width: 240 }}
187187
>
188188
<Menu>
189+
{isFF(FF_HOMEPAGE) && <Menu.Item label="Home" to="/" icon={<IconHome />} data-external exact />}
189190
<Menu.Item label="Projects" to="/projects" icon={<IconFolder />} data-external exact />
190191
<Menu.Item label="Organization" to="/organization" icon={<IconPersonInCircle />} data-external exact />
191-
{isFF(FF_DIA_835) && <Menu.Item label="Models" to={ModelsPage.path} icon={<IconModel />} exact />}
192192

193193
<Menu.Spacer />
194194

web/apps/labelstudio/src/components/Modal/ModalPopup.jsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ const ModalContext = createContext();
1313
export class Modal extends React.Component {
1414
modalRef = React.createRef();
1515

16+
get visible() {
17+
return this.state.visible;
18+
}
19+
1620
constructor(props) {
1721
super(props);
1822

@@ -204,8 +208,8 @@ Modal.Header = ({ children, divided }) => (
204208
</Elem>
205209
);
206210

207-
Modal.Footer = ({ children, bare }) => (
208-
<Elem name="footer" mod={{ bare }}>
211+
Modal.Footer = ({ children, bare, style, className }) => (
212+
<Elem name="footer" mod={{ bare }} mix={className} style={style}>
209213
{children}
210214
</Elem>
211215
);

0 commit comments

Comments
 (0)