mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 04:50:36 +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):
|
||||
''' Serializer: Version create data. '''
|
||||
items = PKField(many=True, required=False, default=None, queryset=Constituenta.objects.all().only('pk'))
|
||||
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
model = Version
|
||||
fields = 'version', 'description'
|
||||
fields = 'version', 'description', 'items'
|
||||
|
||||
|
||||
class LibraryItemDetailsSerializer(serializers.ModelSerializer):
|
||||
|
|
|
@ -6,6 +6,7 @@ from zipfile import ZipFile
|
|||
|
||||
from rest_framework import status
|
||||
|
||||
from apps.library.models import Version
|
||||
from apps.rsform.models import Constituenta, RSForm
|
||||
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']])
|
||||
|
||||
|
||||
@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')
|
||||
def test_retrieve_version(self):
|
||||
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.is_valid(raise_exception=True)
|
||||
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(
|
||||
version=version_input.validated_data['version'],
|
||||
description=version_input.validated_data['description'],
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
IRenameLocationData,
|
||||
ITargetAccessPolicy,
|
||||
ITargetLocation,
|
||||
IVersionData
|
||||
IVersionCreateData
|
||||
} from '@/models/library';
|
||||
import { IRSFormCloneData, IRSFormData, IVersionCreatedResponse } from '@/models/rsform';
|
||||
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({
|
||||
endpoint: `/api/library/${target}/create-version`,
|
||||
request: request
|
||||
|
|
|
@ -92,7 +92,7 @@ export { LuView as IconDBStructure } from 'react-icons/lu';
|
|||
export { LuPlaneTakeoff as IconRESTapi } from 'react-icons/lu';
|
||||
export { LuImage as IconImage } from 'react-icons/lu';
|
||||
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 { LuAtSign as IconTerm } 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 { BiPlusCircle as IconNewItem } from 'react-icons/bi';
|
||||
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 { LuReplace as IconReplace } from 'react-icons/lu';
|
||||
export { FaSortAmountDownAlt as IconSortList } from 'react-icons/fa';
|
||||
|
|
|
@ -3,30 +3,38 @@
|
|||
import clsx from 'clsx';
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import Checkbox from '@/components/ui/Checkbox';
|
||||
import Modal, { ModalProps } from '@/components/ui/Modal';
|
||||
import TextArea from '@/components/ui/TextArea';
|
||||
import TextInput from '@/components/ui/TextInput';
|
||||
import { IVersionData, IVersionInfo } from '@/models/library';
|
||||
import { IVersionCreateData, IVersionInfo } from '@/models/library';
|
||||
import { nextVersion } from '@/models/libraryAPI';
|
||||
import { ConstituentaID } from '@/models/rsform';
|
||||
|
||||
interface DlgCreateVersionProps extends Pick<ModalProps, 'hideWindow'> {
|
||||
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 [description, setDescription] = useState('');
|
||||
const [onlySelected, setOnlySelected] = useState(false);
|
||||
|
||||
const canSubmit = useMemo(() => {
|
||||
return !versions.find(ver => ver.version === version);
|
||||
}, [versions, version]);
|
||||
|
||||
function handleSubmit() {
|
||||
const data: IVersionData = {
|
||||
const data: IVersionCreateData = {
|
||||
version: version,
|
||||
description: description
|
||||
};
|
||||
if (onlySelected) {
|
||||
data.items = selected;
|
||||
}
|
||||
onCreate(data);
|
||||
}
|
||||
|
||||
|
@ -55,6 +63,12 @@ function DlgCreateVersion({ hideWindow, versions, onCreate }: DlgCreateVersionPr
|
|||
value={description}
|
||||
onChange={event => setDescription(event.target.value)}
|
||||
/>
|
||||
<Checkbox
|
||||
id='dlg_only_selected'
|
||||
label={`Только выбранные конституенты [${selected.length} из ${totalCount}]`}
|
||||
value={onlySelected}
|
||||
setValue={value => setOnlySelected(value)}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* Module: Models for LibraryItem.
|
||||
*/
|
||||
|
||||
import { ConstituentaID } from './rsform';
|
||||
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'> {}
|
||||
|
||||
/**
|
||||
* Create version metadata in persistent storage.
|
||||
*/
|
||||
export interface IVersionCreateData extends IVersionData {
|
||||
items?: ConstituentaID[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
return (
|
||||
|
@ -18,7 +18,7 @@ function HelpVersions() {
|
|||
<IconUpload size='1.25rem' className='inline-icon icon-red' /> Загрузить версию в актуальную схему
|
||||
</li>
|
||||
<li>
|
||||
<IconNewItem size='1.25rem' className='inline-icon icon-green' /> Создать версию можно только из актуальной
|
||||
<IconNewVersion size='1.25rem' className='inline-icon icon-green' /> Создать версию можно только из актуальной
|
||||
схемы
|
||||
</li>
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
IconEdit2,
|
||||
IconEditor,
|
||||
IconMenu,
|
||||
IconNewVersion,
|
||||
IconOwner,
|
||||
IconReader,
|
||||
IconShare,
|
||||
|
@ -53,6 +54,9 @@ function HelpRSMenu() {
|
|||
<li>
|
||||
<IconClone className='inline-icon icon-green' /> Клонировать – создать копию схемы
|
||||
</li>
|
||||
<li>
|
||||
<IconNewVersion size='1.25rem' className='inline-icon icon-green' /> Сохранить версию
|
||||
</li>
|
||||
<li>
|
||||
<IconDownload className='inline-icon' /> Выгрузить – сохранить в файле формата Экстеор
|
||||
</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 MiniButton from '@/components/ui/MiniButton';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
|
@ -33,7 +33,7 @@ function ToolbarVersioning({ blockReload }: ToolbarVersioningProps) {
|
|||
titleHtml={controller.isContentEditable ? 'Создать версию' : 'Переключитесь <br/>на актуальную версию'}
|
||||
disabled={!controller.isContentEditable}
|
||||
onClick={controller.createVersion}
|
||||
icon={<IconNewItem size='1.25rem' className='icon-green' />}
|
||||
icon={<IconNewVersion size='1.25rem' className='icon-green' />}
|
||||
/>
|
||||
<MiniButton
|
||||
title={controller.schema?.versions.length === 0 ? 'Список версий пуст' : 'Редактировать версии'}
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
IconLibrary,
|
||||
IconMenu,
|
||||
IconNewItem,
|
||||
IconNewVersion,
|
||||
IconOSS,
|
||||
IconOwner,
|
||||
IconReader,
|
||||
|
@ -156,6 +157,12 @@ function MenuRSTabs({ onDestroy }: MenuRSTabsProps) {
|
|||
onClick={handleClone}
|
||||
/>
|
||||
) : null}
|
||||
<DropdownButton
|
||||
text='Сохранить версию'
|
||||
disabled={!controller.isContentEditable}
|
||||
onClick={controller.createVersion}
|
||||
icon={<IconNewVersion size='1rem' className='icon-green' />}
|
||||
/>
|
||||
<DropdownButton
|
||||
text='Выгрузить в Экстеор'
|
||||
icon={<IconDownload size='1rem' className='icon-primary' />}
|
||||
|
|
|
@ -335,9 +335,8 @@ export const RSEditState = ({
|
|||
if (!model.schema) {
|
||||
return;
|
||||
}
|
||||
model.versionCreate(data, newVersion => {
|
||||
model.versionCreate(data, () => {
|
||||
toast.success(information.newVersion(data.version));
|
||||
viewVersion(newVersion);
|
||||
});
|
||||
},
|
||||
[model, viewVersion]
|
||||
|
@ -725,6 +724,8 @@ export const RSEditState = ({
|
|||
versions={model.schema.versions}
|
||||
hideWindow={() => setShowCreateVersion(false)}
|
||||
onCreate={handleCreateVersion}
|
||||
selected={selected}
|
||||
totalCount={model.schema.items.length}
|
||||
/>
|
||||
) : null}
|
||||
{showEditVersions ? (
|
||||
|
|
Loading…
Reference in New Issue
Block a user