Skip to content

Commit

Permalink
Merge pull request #70 from dave-shawley/project-identifiers
Browse files Browse the repository at this point in the history
Add external identifier management for a project
  • Loading branch information
in-op authored Jan 10, 2024
2 parents c143bd1 + 0037597 commit e8d5cb2
Show file tree
Hide file tree
Showing 9 changed files with 646 additions and 52 deletions.
18 changes: 18 additions & 0 deletions public/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export default {
cancel: 'Cancel',
close: 'Close',
configuration: 'Configuration',
createdAt: 'Created At',
createdBy: 'Created By',
description: 'Description',
delete: 'Delete',
Expand All @@ -21,6 +22,8 @@ export default {
initializing: 'Initializing',
invalidURL: 'Value does not appear to be a URL',
lastUpdated: 'Last Updated: {{date}}',
lastUpdatedBy: 'Last Updated By',
lastUpdatedTitle: 'Last Updated',
loading: 'Loading',
logs: 'Logs',
name: 'Name',
Expand Down Expand Up @@ -65,6 +68,7 @@ export default {
projects: 'Projects',
projectInfo: 'Project Information',
projectFacts: 'Project Facts',
projectIdentifiers: 'Project Identifiers',
projectNote: 'Note',
projectNotes: 'Notes',
projectType: 'Project Type',
Expand Down Expand Up @@ -347,6 +351,20 @@ export default {
repoCreated: 'GitLab Repository Created'
},
id: 'Project ID',
identifiers: {
columns: {
owner: 'Identifier Owner',
externalId: 'Identifier'
},
addIdentifier: 'Add Identifier',
deleteTitle: 'Delete {{integrationName}} identifier',
deleteConfirmation:
'Are you sure you would like to delete this identifier?',
externalId: 'External ID',
integrationName: 'Integration Name',
identifierTitle: 'Identifier for {{integrationName}}',
newIdentifier: 'New Identifier'
},
links: 'Links',
linksSaved: 'Links Saved',
logs: 'Logs',
Expand Down
2 changes: 1 addition & 1 deletion src/js/components/Form/Select.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ function Select({
(multiple === false ? ' truncate pr-6' : '') +
(hasFocus === false && hasError === true ? ' border-red-700' : '')
}
defaultValue={currentValue === null ? '' : currentValue}
value={currentValue === null ? '' : currentValue}
disabled={disabled || readOnly}
id={'field-' + name}
multiple={multiple}
Expand Down
41 changes: 41 additions & 0 deletions src/js/views/Identifiers/Display.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react'
import PropTypes from 'prop-types'
import { DateTime } from 'luxon'
import { Definition } from '../../components/DescriptionList/Definition'
import { DescriptionList } from '../../components/DescriptionList/DescriptionList'
import { useTranslation } from 'react-i18next'

function Display({ entry }) {
const { t } = useTranslation()
return (
<DescriptionList>
<Definition term={t('project.identifiers.integrationName')}>
{entry.integration_name}
</Definition>
<Definition term={t('project.identifiers.externalId')}>
{entry.external_id}
</Definition>
<Definition term={t('common.createdAt')}>
{DateTime.fromISO(entry.created_at).toLocaleString(
DateTime.DATETIME_MED
)}
</Definition>
<Definition term={t('common.createdBy')}>{entry.created_by}</Definition>
<Definition term={t('common.lastUpdatedTitle')}>
{entry.last_modified_at === null
? ''
: DateTime.fromISO(entry.last_modified_at).toLocaleString(
DateTime.DATETIME_MED
)}
</Definition>
<Definition term={t('common.lastUpdatedBy')}>
{entry.last_modified_by === null ? '' : entry.last_modified_by}
</Definition>
</DescriptionList>
)
}

Display.propTypes = {
entry: PropTypes.object.isRequired
}
export { Display }
118 changes: 118 additions & 0 deletions src/js/views/Identifiers/Edit.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import React, { useContext, useEffect, useState } from 'react'
import PropTypes from 'prop-types'
import { ErrorBoundary, Form } from '../../components'
import { httpPatch, ISO8601ToDatetimeLocal } from '../../utils'
import { Context } from '../../state'
import { useTranslation } from 'react-i18next'

function Edit({ integrations, entry, onError, onCancel, onSuccess }) {
const { t } = useTranslation()
const [globalState] = useContext(Context)
const [fieldValues, setFieldValues] = useState()
const [saving, setSaving] = useState(false)

useEffect(() => {
const values = {
...entry,
created_at: ISO8601ToDatetimeLocal(entry.created_at),
last_modified_at: entry.last_modified_at
? ISO8601ToDatetimeLocal(entry.last_modified_at)
: entry.last_modified_at
}
setFieldValues(values)
}, [])

useEffect(() => {
if (!saving) return

let changes = []
if (fieldValues.external_id !== entry.external_id) {
changes = changes.concat([
{
op: 'replace',
path: '/external_id',
value: fieldValues.external_id
}
])
}
if (fieldValues.integration_name !== entry.integration_name) {
changes = changes.concat([
{
op: 'replace',
path: '/integration_name',
value: fieldValues.integration_name
}
])
}
if (changes.length === 0) {
setSaving(false)
onSuccess()
return
}

const update = async () => {
const response = await httpPatch(
globalState.fetch,
new URL(
`/projects/${entry.project_id}/identifiers/${entry.integration_name}`,
globalState.baseURL
),
changes
)
if (response.success) {
setSaving(false)
onSuccess()
} else {
onError(response.data)
}
}
update().catch((error) => onError(error))
}, [saving])

function onValueChange(key, value) {
setFieldValues((prevState) => ({
...prevState,
[key]: value
}))
}

if (!fieldValues) return <></>
return (
<ErrorBoundary>
<Form.SimpleForm
errorMessage={null}
onCancel={onCancel}
onSubmit={() => {
setSaving(true)
}}
ready={true}
saving={saving}>
<Form.Field
title={t('project.identifiers.columns.owner')}
name="integration_name"
type="select"
options={integrations.map((name) => ({ label: name, value: name }))}
value={fieldValues.integration_name}
required={true}
onChange={onValueChange}
/>
<Form.Field
title={t('project.identifiers.columns.externalId')}
name="external_id"
type="text"
value={fieldValues.external_id}
required={true}
onChange={onValueChange}
/>
</Form.SimpleForm>
</ErrorBoundary>
)
}
Edit.propTypes = {
integrations: PropTypes.arrayOf(PropTypes.string).isRequired,
entry: PropTypes.object.isRequired,
onError: PropTypes.func,
onCancel: PropTypes.func.isRequired,
onSuccess: PropTypes.func.isRequired
}
export { Edit }
Loading

0 comments on commit e8d5cb2

Please sign in to comment.