mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
F: Improve versioning UI
This commit is contained in:
parent
c897966886
commit
8bceeb2b38
|
@ -64,10 +64,12 @@ class VersionInnerSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class VersionCreateSerializer(serializers.ModelSerializer):
|
class VersionCreateSerializer(serializers.ModelSerializer):
|
||||||
''' Serializer: Version create data. '''
|
''' Serializer: Version create data. '''
|
||||||
|
items = PKField(many=True, required=False, default=None, queryset=Constituenta.objects.all().only('pk'))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
''' serializer metadata. '''
|
''' serializer metadata. '''
|
||||||
model = Version
|
model = Version
|
||||||
fields = 'version', 'description'
|
fields = 'version', 'description', 'items'
|
||||||
|
|
||||||
|
|
||||||
class LibraryItemDetailsSerializer(serializers.ModelSerializer):
|
class LibraryItemDetailsSerializer(serializers.ModelSerializer):
|
||||||
|
|
|
@ -6,6 +6,7 @@ from zipfile import ZipFile
|
||||||
|
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
|
from apps.library.models import Version
|
||||||
from apps.rsform.models import Constituenta, RSForm
|
from apps.rsform.models import Constituenta, RSForm
|
||||||
from shared.EndpointTester import EndpointTester, decl_endpoint
|
from shared.EndpointTester import EndpointTester, decl_endpoint
|
||||||
|
|
||||||
|
@ -41,6 +42,20 @@ class TestVersionViews(EndpointTester):
|
||||||
self.assertTrue(response.data['version'] in [v['id'] for v in response.data['schema']['versions']])
|
self.assertTrue(response.data['version'] in [v['id'] for v in response.data['schema']['versions']])
|
||||||
|
|
||||||
|
|
||||||
|
@decl_endpoint('/api/library/{schema}/create-version', method='post')
|
||||||
|
def test_create_version_filter(self):
|
||||||
|
x2 = self.owned.insert_new('X2')
|
||||||
|
data = {'version': '1.0.0', 'description': 'test', 'items': [x2.pk]}
|
||||||
|
response = self.executeCreated(data=data, schema=self.owned_id)
|
||||||
|
version = Version.objects.get(pk=response.data['version'])
|
||||||
|
items = version.data['items']
|
||||||
|
self.assertTrue('version' in response.data)
|
||||||
|
self.assertTrue('schema' in response.data)
|
||||||
|
self.assertTrue(response.data['version'] in [v['id'] for v in response.data['schema']['versions']])
|
||||||
|
self.assertEqual(len(items), 1)
|
||||||
|
self.assertEqual(items[0]['id'], x2.pk)
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/library/{schema}/versions/{version}', method='get')
|
@decl_endpoint('/api/library/{schema}/versions/{version}', method='get')
|
||||||
def test_retrieve_version(self):
|
def test_retrieve_version(self):
|
||||||
version_id = self._create_version({'version': '1.0.0', 'description': 'test'})
|
version_id = self._create_version({'version': '1.0.0', 'description': 'test'})
|
||||||
|
|
|
@ -103,6 +103,9 @@ def create_version(request: Request, pk_item: int) -> HttpResponse:
|
||||||
version_input = s.VersionCreateSerializer(data=request.data)
|
version_input = s.VersionCreateSerializer(data=request.data)
|
||||||
version_input.is_valid(raise_exception=True)
|
version_input.is_valid(raise_exception=True)
|
||||||
data = RSFormSerializer(item).to_versioned_data()
|
data = RSFormSerializer(item).to_versioned_data()
|
||||||
|
items: list[int] = [] if 'items' not in request.data else request.data['items']
|
||||||
|
if items:
|
||||||
|
data['items'] = [cst for cst in data['items'] if cst['id'] in items]
|
||||||
result = RSForm(item).create_version(
|
result = RSForm(item).create_version(
|
||||||
version=version_input.validated_data['version'],
|
version=version_input.validated_data['version'],
|
||||||
description=version_input.validated_data['description'],
|
description=version_input.validated_data['description'],
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
IRenameLocationData,
|
IRenameLocationData,
|
||||||
ITargetAccessPolicy,
|
ITargetAccessPolicy,
|
||||||
ITargetLocation,
|
ITargetLocation,
|
||||||
IVersionData
|
IVersionCreateData
|
||||||
} from '@/models/library';
|
} from '@/models/library';
|
||||||
import { IRSFormCloneData, IRSFormData, IVersionCreatedResponse } from '@/models/rsform';
|
import { IRSFormCloneData, IRSFormData, IVersionCreatedResponse } from '@/models/rsform';
|
||||||
import { ITargetUser, ITargetUsers } from '@/models/user';
|
import { ITargetUser, ITargetUsers } from '@/models/user';
|
||||||
|
@ -109,7 +109,7 @@ export function patchSetEditors(target: string, request: FrontPush<ITargetUsers>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function postCreateVersion(target: string, request: FrontExchange<IVersionData, IVersionCreatedResponse>) {
|
export function postCreateVersion(target: string, request: FrontExchange<IVersionCreateData, IVersionCreatedResponse>) {
|
||||||
AxiosPost({
|
AxiosPost({
|
||||||
endpoint: `/api/library/${target}/create-version`,
|
endpoint: `/api/library/${target}/create-version`,
|
||||||
request: request
|
request: request
|
||||||
|
|
|
@ -92,7 +92,7 @@ export { LuView as IconDBStructure } from 'react-icons/lu';
|
||||||
export { LuPlaneTakeoff as IconRESTapi } from 'react-icons/lu';
|
export { LuPlaneTakeoff as IconRESTapi } from 'react-icons/lu';
|
||||||
export { LuImage as IconImage } from 'react-icons/lu';
|
export { LuImage as IconImage } from 'react-icons/lu';
|
||||||
export { TbColumns as IconList } from 'react-icons/tb';
|
export { TbColumns as IconList } from 'react-icons/tb';
|
||||||
export { ImStack as IconVersions } from 'react-icons/im';
|
export { GoVersions as IconVersions } from 'react-icons/go';
|
||||||
export { TbColumnsOff as IconListOff } from 'react-icons/tb';
|
export { TbColumnsOff as IconListOff } from 'react-icons/tb';
|
||||||
export { LuAtSign as IconTerm } from 'react-icons/lu';
|
export { LuAtSign as IconTerm } from 'react-icons/lu';
|
||||||
export { LuSubscript as IconAlias } from 'react-icons/lu';
|
export { LuSubscript as IconAlias } from 'react-icons/lu';
|
||||||
|
@ -122,6 +122,7 @@ export { BiLeftArrow as IconMoveLeft } from 'react-icons/bi';
|
||||||
export { TbHexagonPlus2 as IconNewRSForm } from 'react-icons/tb';
|
export { TbHexagonPlus2 as IconNewRSForm } from 'react-icons/tb';
|
||||||
export { BiPlusCircle as IconNewItem } from 'react-icons/bi';
|
export { BiPlusCircle as IconNewItem } from 'react-icons/bi';
|
||||||
export { FaSquarePlus as IconNewItem2 } from 'react-icons/fa6';
|
export { FaSquarePlus as IconNewItem2 } from 'react-icons/fa6';
|
||||||
|
export { PiStackPlus as IconNewVersion } from 'react-icons/pi';
|
||||||
export { BiDuplicate as IconClone } from 'react-icons/bi';
|
export { BiDuplicate as IconClone } from 'react-icons/bi';
|
||||||
export { LuReplace as IconReplace } from 'react-icons/lu';
|
export { LuReplace as IconReplace } from 'react-icons/lu';
|
||||||
export { FaSortAmountDownAlt as IconSortList } from 'react-icons/fa';
|
export { FaSortAmountDownAlt as IconSortList } from 'react-icons/fa';
|
||||||
|
|
|
@ -3,30 +3,38 @@
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import Checkbox from '@/components/ui/Checkbox';
|
||||||
import Modal, { ModalProps } from '@/components/ui/Modal';
|
import Modal, { ModalProps } from '@/components/ui/Modal';
|
||||||
import TextArea from '@/components/ui/TextArea';
|
import TextArea from '@/components/ui/TextArea';
|
||||||
import TextInput from '@/components/ui/TextInput';
|
import TextInput from '@/components/ui/TextInput';
|
||||||
import { IVersionData, IVersionInfo } from '@/models/library';
|
import { IVersionCreateData, IVersionInfo } from '@/models/library';
|
||||||
import { nextVersion } from '@/models/libraryAPI';
|
import { nextVersion } from '@/models/libraryAPI';
|
||||||
|
import { ConstituentaID } from '@/models/rsform';
|
||||||
|
|
||||||
interface DlgCreateVersionProps extends Pick<ModalProps, 'hideWindow'> {
|
interface DlgCreateVersionProps extends Pick<ModalProps, 'hideWindow'> {
|
||||||
versions: IVersionInfo[];
|
versions: IVersionInfo[];
|
||||||
onCreate: (data: IVersionData) => void;
|
onCreate: (data: IVersionCreateData) => void;
|
||||||
|
selected: ConstituentaID[];
|
||||||
|
totalCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DlgCreateVersion({ hideWindow, versions, onCreate }: DlgCreateVersionProps) {
|
function DlgCreateVersion({ hideWindow, versions, selected, totalCount, onCreate }: DlgCreateVersionProps) {
|
||||||
const [version, setVersion] = useState(versions.length > 0 ? nextVersion(versions[0].version) : '1.0.0');
|
const [version, setVersion] = useState(versions.length > 0 ? nextVersion(versions[0].version) : '1.0.0');
|
||||||
const [description, setDescription] = useState('');
|
const [description, setDescription] = useState('');
|
||||||
|
const [onlySelected, setOnlySelected] = useState(false);
|
||||||
|
|
||||||
const canSubmit = useMemo(() => {
|
const canSubmit = useMemo(() => {
|
||||||
return !versions.find(ver => ver.version === version);
|
return !versions.find(ver => ver.version === version);
|
||||||
}, [versions, version]);
|
}, [versions, version]);
|
||||||
|
|
||||||
function handleSubmit() {
|
function handleSubmit() {
|
||||||
const data: IVersionData = {
|
const data: IVersionCreateData = {
|
||||||
version: version,
|
version: version,
|
||||||
description: description
|
description: description
|
||||||
};
|
};
|
||||||
|
if (onlySelected) {
|
||||||
|
data.items = selected;
|
||||||
|
}
|
||||||
onCreate(data);
|
onCreate(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +63,12 @@ function DlgCreateVersion({ hideWindow, versions, onCreate }: DlgCreateVersionPr
|
||||||
value={description}
|
value={description}
|
||||||
onChange={event => setDescription(event.target.value)}
|
onChange={event => setDescription(event.target.value)}
|
||||||
/>
|
/>
|
||||||
|
<Checkbox
|
||||||
|
id='dlg_only_selected'
|
||||||
|
label={`Только выбранные конституенты [${selected.length} из ${totalCount}]`}
|
||||||
|
value={onlySelected}
|
||||||
|
setValue={value => setOnlySelected(value)}
|
||||||
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
* Module: Models for LibraryItem.
|
* Module: Models for LibraryItem.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { ConstituentaID } from './rsform';
|
||||||
import { UserID } from './user';
|
import { UserID } from './user';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -52,10 +53,17 @@ export interface IVersionInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents user data, intended to create or update version metadata in persistent storage.
|
* Represents version data, intended to update version metadata in persistent storage.
|
||||||
*/
|
*/
|
||||||
export interface IVersionData extends Omit<IVersionInfo, 'id' | 'time_create'> {}
|
export interface IVersionData extends Omit<IVersionInfo, 'id' | 'time_create'> {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create version metadata in persistent storage.
|
||||||
|
*/
|
||||||
|
export interface IVersionCreateData extends IVersionData {
|
||||||
|
items?: ConstituentaID[];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents library item common data typical for all item types.
|
* Represents library item common data typical for all item types.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { IconEditor, IconNewItem, IconShare, IconUpload, IconVersions } from '@/components/Icons';
|
import { IconEditor, IconNewVersion, IconShare, IconUpload, IconVersions } from '@/components/Icons';
|
||||||
|
|
||||||
function HelpVersions() {
|
function HelpVersions() {
|
||||||
return (
|
return (
|
||||||
|
@ -18,7 +18,7 @@ function HelpVersions() {
|
||||||
<IconUpload size='1.25rem' className='inline-icon icon-red' /> Загрузить версию в актуальную схему
|
<IconUpload size='1.25rem' className='inline-icon icon-red' /> Загрузить версию в актуальную схему
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<IconNewItem size='1.25rem' className='inline-icon icon-green' /> Создать версию можно только из актуальной
|
<IconNewVersion size='1.25rem' className='inline-icon icon-green' /> Создать версию можно только из актуальной
|
||||||
схемы
|
схемы
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
IconEdit2,
|
IconEdit2,
|
||||||
IconEditor,
|
IconEditor,
|
||||||
IconMenu,
|
IconMenu,
|
||||||
|
IconNewVersion,
|
||||||
IconOwner,
|
IconOwner,
|
||||||
IconReader,
|
IconReader,
|
||||||
IconShare,
|
IconShare,
|
||||||
|
@ -53,6 +54,9 @@ function HelpRSMenu() {
|
||||||
<li>
|
<li>
|
||||||
<IconClone className='inline-icon icon-green' /> Клонировать – создать копию схемы
|
<IconClone className='inline-icon icon-green' /> Клонировать – создать копию схемы
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<IconNewVersion size='1.25rem' className='inline-icon icon-green' /> Сохранить версию
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<IconDownload className='inline-icon' /> Выгрузить – сохранить в файле формата Экстеор
|
<IconDownload className='inline-icon' /> Выгрузить – сохранить в файле формата Экстеор
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { IconNewItem, IconUpload, IconVersions } from '@/components/Icons';
|
import { IconNewVersion, IconUpload, IconVersions } from '@/components/Icons';
|
||||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||||
import MiniButton from '@/components/ui/MiniButton';
|
import MiniButton from '@/components/ui/MiniButton';
|
||||||
import Overlay from '@/components/ui/Overlay';
|
import Overlay from '@/components/ui/Overlay';
|
||||||
|
@ -33,7 +33,7 @@ function ToolbarVersioning({ blockReload }: ToolbarVersioningProps) {
|
||||||
titleHtml={controller.isContentEditable ? 'Создать версию' : 'Переключитесь <br/>на актуальную версию'}
|
titleHtml={controller.isContentEditable ? 'Создать версию' : 'Переключитесь <br/>на актуальную версию'}
|
||||||
disabled={!controller.isContentEditable}
|
disabled={!controller.isContentEditable}
|
||||||
onClick={controller.createVersion}
|
onClick={controller.createVersion}
|
||||||
icon={<IconNewItem size='1.25rem' className='icon-green' />}
|
icon={<IconNewVersion size='1.25rem' className='icon-green' />}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
title={controller.schema?.versions.length === 0 ? 'Список версий пуст' : 'Редактировать версии'}
|
title={controller.schema?.versions.length === 0 ? 'Список версий пуст' : 'Редактировать версии'}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
IconLibrary,
|
IconLibrary,
|
||||||
IconMenu,
|
IconMenu,
|
||||||
IconNewItem,
|
IconNewItem,
|
||||||
|
IconNewVersion,
|
||||||
IconOSS,
|
IconOSS,
|
||||||
IconOwner,
|
IconOwner,
|
||||||
IconReader,
|
IconReader,
|
||||||
|
@ -156,6 +157,12 @@ function MenuRSTabs({ onDestroy }: MenuRSTabsProps) {
|
||||||
onClick={handleClone}
|
onClick={handleClone}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
<DropdownButton
|
||||||
|
text='Сохранить версию'
|
||||||
|
disabled={!controller.isContentEditable}
|
||||||
|
onClick={controller.createVersion}
|
||||||
|
icon={<IconNewVersion size='1rem' className='icon-green' />}
|
||||||
|
/>
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
text='Выгрузить в Экстеор'
|
text='Выгрузить в Экстеор'
|
||||||
icon={<IconDownload size='1rem' className='icon-primary' />}
|
icon={<IconDownload size='1rem' className='icon-primary' />}
|
||||||
|
|
|
@ -335,9 +335,8 @@ export const RSEditState = ({
|
||||||
if (!model.schema) {
|
if (!model.schema) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
model.versionCreate(data, newVersion => {
|
model.versionCreate(data, () => {
|
||||||
toast.success(information.newVersion(data.version));
|
toast.success(information.newVersion(data.version));
|
||||||
viewVersion(newVersion);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[model, viewVersion]
|
[model, viewVersion]
|
||||||
|
@ -725,6 +724,8 @@ export const RSEditState = ({
|
||||||
versions={model.schema.versions}
|
versions={model.schema.versions}
|
||||||
hideWindow={() => setShowCreateVersion(false)}
|
hideWindow={() => setShowCreateVersion(false)}
|
||||||
onCreate={handleCreateVersion}
|
onCreate={handleCreateVersion}
|
||||||
|
selected={selected}
|
||||||
|
totalCount={model.schema.items.length}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{showEditVersions ? (
|
{showEditVersions ? (
|
||||||
|
|
Loading…
Reference in New Issue
Block a user