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
110 changes: 81 additions & 29 deletions src/components/repos/VersionForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { withTranslation } from 'react-i18next';
import moment from 'moment';

import { TextField, FormControlLabel, Checkbox, Autocomplete } from '@mui/material';
import { TextField, FormControlLabel, Checkbox, Autocomplete, DialogContent, RadioGroup, Radio, Typography } from '@mui/material';
import { set, get, startCase, isBoolean, isObject, values, map } from 'lodash';
import APIService from '../../services/APIService';
import GAService from '../../services/GAService';
Expand All @@ -13,7 +13,12 @@ import {
} from '../../common/validators';
import Button from '../common/Button'
import CloseIconButton from '../common/CloseIconButton';
import Dialog from '../common/Dialog'
import DialogTitle from '../common/DialogTitle'
import { ID_REGEX } from '../../common/constants'
import {
shouldAskForExpansion, shouldShowAutoexpand, getExpansionChoices, getDefaultExpansion, getExpansionPayload
} from './versionExpansion'


class VersionForm extends FormComponent {
Expand All @@ -35,6 +40,8 @@ class VersionForm extends FormComponent {
expansion_url: {...fieldStruct},
revision_date: {...fieldStruct},
},
expansionDialog: false,
expansion: null,
}
}

Expand All @@ -57,37 +64,58 @@ class VersionForm extends FormComponent {

onCheckboxChange = event => this.setFieldValue(event.target.name, event.target.checked)

isCollectionVersion = () => (this.props.resource || this.props.resourceType) === 'collection'

getExpansionArgs = () => ({
edit: Boolean(this.props.edit),
isCollection: this.isCollectionVersion(),
lockToggle: Boolean(this.context?.toggles?.EXPANSION_LOCKED_REPO_VERSION_TOGGLE),
headExpansionURL: this.props.version?.expansion_url,
})

onSubmit = event => {
event.preventDefault();
event.stopPropagation();
const isValid = this.setAllFieldsErrors()
if(isValid) {
const { version, edit, resource, resourceType } = this.props
if(version?.url) {
const isCollectionVersion = (resource || resourceType) === 'collection'
const payload = this.getValues()

if(isBoolean(this.state.fields.released.value))
payload.released = this.state.fields.released.value
if(this.state.fields.revision_date.value)
payload.revision_date = moment(payload.revision_date).utc().format('YYYY-MM-DD HH:mm:ss')
else
delete payload.revision_date

if(isCollectionVersion) {
payload.autoexpand = isBoolean(this.state.fields.autoexpand.value) ? this.state.fields.autoexpand.value : false
payload.expansion_url = this.state.fields.expansion_url.value || null
} else {
delete payload.autoexpand
delete payload.expansion_url
}

const url = edit ? version.version_url : version.url
GAService.recordUpsertEvent(isCollectionVersion ? 'Collection Version' : 'Source Version', edit)
let service = APIService.new().overrideURL(url)
service = edit ? service.put(payload) : service.appendToUrl('versions/').post(payload)
service.then(response => this.handleSubmitResponse(response))
if(this.setAllFieldsErrors()) {
if(shouldAskForExpansion(this.getExpansionArgs()))
this.setState({expansionDialog: true, expansion: getDefaultExpansion(this.getExpansionArgs())})
else
this.submit()
}
}

onExpansionSubmit = () => {
this.setState({expansionDialog: false})
this.submit(this.state.expansion)
}

submit = expansion => {
const { version, edit } = this.props
if(version?.url) {
const isCollectionVersion = this.isCollectionVersion()
const payload = this.getValues()

if(isBoolean(this.state.fields.released.value))
payload.released = this.state.fields.released.value
if(this.state.fields.revision_date.value)
payload.revision_date = moment(payload.revision_date).utc().format('YYYY-MM-DD HH:mm:ss')
else
delete payload.revision_date

if(isCollectionVersion) {
payload.autoexpand = isBoolean(this.state.fields.autoexpand.value) ? this.state.fields.autoexpand.value : false
payload.expansion_url = this.state.fields.expansion_url.value || null
Object.assign(payload, getExpansionPayload(expansion))
} else {
delete payload.autoexpand
delete payload.expansion_url
}

const url = edit ? version.version_url : version.url
GAService.recordUpsertEvent(isCollectionVersion ? 'Collection Version' : 'Source Version', edit)
let service = APIService.new().overrideURL(url)
service = edit ? service.put(payload) : service.appendToUrl('versions/').post(payload)
service.then(response => this.handleSubmitResponse(response))
}
}

Expand Down Expand Up @@ -206,7 +234,7 @@ class VersionForm extends FormComponent {
/>
</div>
{
resource === 'collection' &&
resource === 'collection' && shouldShowAutoexpand(this.getExpansionArgs()) &&
<div className='col-xs-12' style={{width: '100%', marginTop: '15px'}}>
<FormControlLabel
control={<Checkbox checked={Boolean(fields.autoexpand.value)} onChange={this.onCheckboxChange} name="autoexpand" />}
Expand All @@ -220,6 +248,30 @@ class VersionForm extends FormComponent {
</div>
</form>
</div>
<Dialog open={this.state.expansionDialog} onClose={() => this.setState({expansionDialog: false})} fullWidth maxWidth='xs'>
<DialogTitle>{t('repo.version.expansion_dialog.title')}</DialogTitle>
<DialogContent sx={{padding: '16px 0 0 0 !important'}}>
<RadioGroup value={this.state.expansion || ''} onChange={event => this.setState({expansion: event.target.value})}>
{
map(getExpansionChoices(this.getExpansionArgs()), choice => (
<FormControlLabel
key={choice}
value={choice}
control={<Radio />}
sx={{alignItems: 'flex-start', marginBottom: '8px', '& .MuiRadio-root': {paddingTop: '2px'}}}
label={
<span>
<Typography component='span' sx={{display: 'block'}}>{t(`repo.version.expansion_dialog.${choice}`)}</Typography>
<Typography component='span' variant='body2' sx={{display: 'block', color: 'text.secondary'}}>{t(`repo.version.expansion_dialog.${choice}_help`)}</Typography>
</span>
}
/>
))
}
</RadioGroup>
<Button sx={{marginTop: '16px'}} color='primary' label={t('common.submit')} onClick={this.onExpansionSubmit} />
</DialogContent>
</Dialog>
</div>
)
}
Expand Down
50 changes: 50 additions & 0 deletions src/components/repos/__tests__/versionExpansion.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import test from 'node:test'
import assert from 'node:assert/strict'

const {
shouldAskForExpansion, shouldShowAutoexpand, getExpansionChoices, getDefaultExpansion, getExpansionPayload
} = await import('../versionExpansion.js')

const create = {edit: false, isCollection: true}
const head = '/orgs/MyOrg/collections/MyCollection/HEAD/expansions/autoexpand-HEAD/'

test('with the lock toggle off, creating a version keeps the autoexpand checkbox and asks nothing', () => {
const args = {...create, lockToggle: false, headExpansionURL: head}
assert.equal(shouldAskForExpansion(args), false)
assert.equal(shouldShowAutoexpand(args), true)
assert.deepEqual(getExpansionPayload(undefined), {})
})

test('with the lock toggle on and no HEAD expansion, the choices are latest and none, latest first', () => {
const args = {...create, lockToggle: true, headExpansionURL: null}
assert.equal(shouldAskForExpansion(args), true)
assert.equal(shouldShowAutoexpand(args), false)
assert.deepEqual(getExpansionChoices(args), ['latest', 'none'])
assert.equal(getDefaultExpansion(args), 'latest')
})

test('with the lock toggle on and a HEAD expansion, lock is offered and is the default', () => {
const args = {...create, lockToggle: true, headExpansionURL: head}
assert.deepEqual(getExpansionChoices(args), ['lock', 'latest', 'none'])
assert.equal(getDefaultExpansion(args), 'lock')
})

test('editing a version asks nothing and shows the checkbox, whatever the toggle', () => {
for(const lockToggle of [true, false]) {
const args = {edit: true, isCollection: true, lockToggle, headExpansionURL: head}
assert.equal(shouldAskForExpansion(args), false)
assert.equal(shouldShowAutoexpand(args), true)
}
})

test('source versions get neither the popup nor the checkbox', () => {
const args = {edit: false, isCollection: false, lockToggle: true, headExpansionURL: head}
assert.equal(shouldAskForExpansion(args), false)
assert.equal(shouldShowAutoexpand(args), false)
})

test('each choice maps to its expansion and autoexpand pair', () => {
assert.deepEqual(getExpansionPayload('lock'), {expansion: 'lock', autoexpand: true})
assert.deepEqual(getExpansionPayload('latest'), {expansion: 'latest', autoexpand: true})
assert.deepEqual(getExpansionPayload('none'), {expansion: 'none', autoexpand: false})
})
11 changes: 11 additions & 0 deletions src/components/repos/versionExpansion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// How a new collection version's expansion is chosen. The popup is asked only with the lock toggle on.
export const shouldAskForExpansion = ({edit, isCollection, lockToggle}) => Boolean(!edit && isCollection && lockToggle)

export const shouldShowAutoexpand = ({edit, isCollection, lockToggle}) => Boolean(isCollection && (edit || !lockToggle))

export const getExpansionChoices = ({lockToggle, headExpansionURL}) =>
lockToggle && headExpansionURL ? ['lock', 'latest', 'none'] : ['latest', 'none']

export const getDefaultExpansion = args => getExpansionChoices(args)[0]

export const getExpansionPayload = expansion => expansion ? {expansion, autoexpand: expansion !== 'none'} : {}
9 changes: 9 additions & 0 deletions src/i18n/locales/en/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,15 @@
"new_title": "New {{resourceType}} Version: {{version}}",
"edit_title": "Edit {{resourceType}} Version: {{version}}"
},
"expansion_dialog": {
"title": "Expansion for this version",
"lock": "Lock repo versions",
"lock_help": "Uses the same repository versions as HEAD's expansion. Source versions are saved in the new expansion's parameters, so a rebuild keeps them. Value sets referenced without a version can resolve again on a rebuild.",
"latest": "Evaluate from scratch",
"latest_help": "Evaluates every reference again. References without a version use each repository's latest released version, or its HEAD if it has no release.",
"none": "No expansion",
"none_help": "Creates the version without an expansion. You can add one later."
},
"update_dialog": {
"title": "Update {{resourceType}}: {{resourceId}}",
"message": "Are you sure you want to {{action}} this {{resourceType}} {{repoId}}?"
Expand Down
9 changes: 9 additions & 0 deletions src/i18n/locales/es/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,15 @@
"form": {
"new_title": "Nueva versión de {{resourceType}}: {{version}}",
"edit_title": "Editar versión de {{resourceType}}: {{version}}"
},
"expansion_dialog": {
"title": "Expansión para esta versión",
"lock": "Fijar versiones de repositorio",
"lock_help": "Usa las mismas versiones de repositorio que la expansión de HEAD. Las versiones de fuentes se guardan en los parámetros de la nueva expansión, así que una reconstrucción las conserva. Los conjuntos de valores referenciados sin versión pueden resolverse de nuevo al reconstruir.",
"latest": "Evaluar desde cero",
"latest_help": "Evalúa de nuevo todas las referencias. Las referencias sin versión usan la última versión publicada de cada repositorio, o su HEAD si no tiene ninguna.",
"none": "Sin expansión",
"none_help": "Crea la versión sin expansión. Puede agregar una más tarde."
}
},
"repo_version": "Versión del repositorio"
Expand Down
9 changes: 9 additions & 0 deletions src/i18n/locales/pt-BR/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,15 @@
"new_title": "Nova versão de {{resourceType}}: {{version}}",
"edit_title": "Editar versão de {{resourceType}}: {{version}}"
},
"expansion_dialog": {
"title": "Expansão para esta versão",
"lock": "Fixar versões de repositório",
"lock_help": "Usa as mesmas versões de repositório da expansão do HEAD. As versões de fontes ficam salvas nos parâmetros da nova expansão, então uma reconstrução as mantém. Conjuntos de valores referenciados sem versão podem ser resolvidos de novo ao reconstruir.",
"latest": "Avaliar do zero",
"latest_help": "Avalia todas as referências novamente. Referências sem versão usam a versão publicada mais recente de cada repositório, ou o HEAD se não houver nenhuma.",
"none": "Sem expansão",
"none_help": "Cria a versão sem expansão. Você pode adicionar uma depois."
},
"update_dialog": {
"title": "Atualizar {{resourceType}}: {{resourceId}}",
"message": "Tem certeza de que deseja {{action}} este {{resourceType}} {{repoId}}?"
Expand Down
9 changes: 9 additions & 0 deletions src/i18n/locales/zh/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,15 @@
"form": {
"new_title": "新建{{resourceType}}版本:{{version}}",
"edit_title": "编辑{{resourceType}}版本:{{version}}"
},
"expansion_dialog": {
"title": "此版本的扩展",
"lock": "锁定存储库版本",
"lock_help": "使用与 HEAD 扩展相同的存储库版本。源版本保存在新扩展的参数中,因此重建会保留它们。未指定版本引用的值集在重建时可能会重新解析。",
"latest": "从头评估",
"latest_help": "重新评估每个引用。没有版本的引用使用每个存储库最新发布的版本;如果没有发布版本,则使用其 HEAD。",
"none": "不扩展",
"none_help": "创建不带扩展的版本。之后可以再添加。"
}
},
"repo_version": "仓库版本"
Expand Down
Loading