Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add click and drag to gene glyphs #269

Merged
merged 10 commits into from
Oct 24, 2023
87 changes: 47 additions & 40 deletions packages/apollo-common/src/AssemblySpecificChange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,49 +199,47 @@ export abstract class AssemblySpecificChange extends Change {
const { featureModel, refSeqModel, user } = backend
const { assembly, logger, refSeqCache } = this

for (const featureLine of gff3Feature) {
const { seq_id: refName } = featureLine
if (!refName) {
throw new Error(
`Valid seq_id not found in feature ${JSON.stringify(featureLine)}`,
)
}
let refSeqDoc = refSeqCache.get(refName)
if (!refSeqDoc) {
refSeqDoc =
(await refSeqModel.findOne({ assembly, name: refName }).exec()) ??
undefined
if (refSeqDoc) {
refSeqCache.set(refName, refSeqDoc)
}
}
if (!refSeqDoc) {
throw new Error(
`RefSeq was not found by assembly "${assembly}" and seq_id "${refName}" not found`,
)
const [{ seq_id: refName }] = gff3Feature
if (!refName) {
throw new Error(
`Valid seq_id not found in feature ${JSON.stringify(gff3Feature)}`,
)
}
let refSeqDoc = refSeqCache.get(refName)
if (!refSeqDoc) {
refSeqDoc =
(await refSeqModel.findOne({ assembly, name: refName }).exec()) ??
undefined
if (refSeqDoc) {
refSeqCache.set(refName, refSeqDoc)
}
// Let's add featureId to parent feature
const featureIds: string[] = []

const newFeature = createFeature(gff3Feature, refSeqDoc._id, featureIds)
logger.debug?.(`So far feature ids are: ${featureIds.toString()}`)
// Add value to gffId
newFeature.attributes?._id
? (newFeature.gffId = newFeature.attributes?._id.toString())
: (newFeature.gffId = newFeature._id)
logger.debug?.(
`********************* Assembly specific change create ${JSON.stringify(
newFeature,
)}`,
}
if (!refSeqDoc) {
throw new Error(
`RefSeq was not found by assembly "${assembly}" and seq_id "${refName}" not found`,
)

// Add into Mongo
// We cannot use Mongo 'session' / transaction here because Mongo has 16 MB limit for transaction
const [newFeatureDoc] = await featureModel.create([
{ allIds: featureIds, ...newFeature, user, status: -1 },
])
logger.verbose?.(`Added docId "${newFeatureDoc._id}"`)
}
// Let's add featureId to parent feature
const featureIds: string[] = []

const newFeature = createFeature(gff3Feature, refSeqDoc._id, featureIds)
logger.debug?.(`So far feature ids are: ${featureIds.toString()}`)
// Add value to gffId
newFeature.attributes?._id
? (newFeature.gffId = newFeature.attributes?._id.toString())
: (newFeature.gffId = newFeature._id)
logger.debug?.(
`********************* Assembly specific change create ${JSON.stringify(
newFeature,
)}`,
)

// Add into Mongo
// We cannot use Mongo 'session' / transaction here because Mongo has 16 MB limit for transaction
const [newFeatureDoc] = await featureModel.create([
{ allIds: featureIds, ...newFeature, user, status: -1 },
])
logger.verbose?.(`Added docId "${newFeatureDoc._id}"`)
}
}

Expand Down Expand Up @@ -292,6 +290,15 @@ function createFeature(
end,
}
if (gff3Feature.length > 1) {
const lastEnd = Math.max(
...gff3Feature.map((f) => {
if (f.end === null) {
throw new Error(`feature does not have end: ${JSON.stringify(f)}`)
}
return f.end
}),
)
feature.end = lastEnd
feature.discontinuousLocations = gff3Feature.map((f) => {
const { end: subEnd, phase: locationPhase, start: subStart } = f
if (subStart === null || subEnd === null) {
Expand Down
18 changes: 18 additions & 0 deletions packages/apollo-mst/src/AnnotationFeature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,15 @@ export const AnnotationFeature = types
self.start = start
}
},
setCDSDiscontinuousLocationStart(start: number, index: number) {
const dl = self.discontinuousLocations
if (dl && dl.length > 0 && dl[index].start !== start) {
dl[index].start = start
if (index === 0) {
self.start = start
}
}
},
setEnd(end: number) {
if (end < self.start) {
throw new Error(`End "${end}" is less than start "${self.start}"`)
Expand All @@ -121,6 +130,15 @@ export const AnnotationFeature = types
self.end = end
}
},
setCDSDiscontinuousLocationEnd(end: number, index: number) {
const dl = self.discontinuousLocations
if (dl && dl.length > 0 && dl[index].end !== end) {
dl[index].end = end
if (index === dl.length - 1) {
self.end = end
}
}
},
setStrand(strand?: 1 | -1) {
self.strand = strand
},
Expand Down
175 changes: 175 additions & 0 deletions packages/apollo-shared/src/Changes/DiscontinuousLocationEndChange.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import {
ChangeOptions,
ClientDataStore,
FeatureChange,
LocalGFF3DataStore,
SerializedFeatureChange,
ServerDataStore,
} from 'apollo-common'

interface SerializedDiscontinuousLocationEndChangeBase
extends SerializedFeatureChange {
typeName: 'DiscontinuousLocationEndChange'
}

interface DiscontinuousLocationEndChangeDetails {
featureId: string
oldEnd: number
newEnd: number
index: number
}

interface SerializedDiscontinuousLocationEndChangeSingle
extends SerializedDiscontinuousLocationEndChangeBase,
DiscontinuousLocationEndChangeDetails {}

interface SerializedDiscontinuousLocationEndChangeMultiple
extends SerializedDiscontinuousLocationEndChangeBase {
changes: DiscontinuousLocationEndChangeDetails[]
}

type SerializedDiscontinuousLocationEndChange =
| SerializedDiscontinuousLocationEndChangeSingle
| SerializedDiscontinuousLocationEndChangeMultiple

export class DiscontinuousLocationEndChange extends FeatureChange {
typeName = 'DiscontinuousLocationEndChange' as const
changes: DiscontinuousLocationEndChangeDetails[]

constructor(
json: SerializedDiscontinuousLocationEndChange,
options?: ChangeOptions,
) {
super(json, options)
this.changes = 'changes' in json ? json.changes : [json]
}

toJSON(): SerializedDiscontinuousLocationEndChange {
const { assembly, changedIds, changes, typeName } = this
if (changes.length === 1) {
const [{ featureId, index, newEnd, oldEnd }] = changes
return {
typeName,
changedIds,
assembly,
featureId,
oldEnd,
newEnd,
index,
}
}
return { typeName, changedIds, assembly, changes }
}

async executeOnServer(backend: ServerDataStore) {
const { featureModel, session } = backend
const { changes, logger } = this
for (const change of changes) {
const { featureId, index, newEnd, oldEnd: expectedOldEnd } = change
const topLevelFeature = await featureModel
.findOne({ allIds: featureId })
.session(session)
.exec()

if (!topLevelFeature) {
const errMsg = `ERROR: The following featureId was not found in database ='${featureId}'`
logger.error(errMsg)
throw new Error(errMsg)
}

const feature = this.getFeatureFromId(topLevelFeature, featureId)
if (!feature) {
const errMsg = 'ERROR when searching feature by featureId'
logger.error(errMsg)
throw new Error(errMsg)
}
logger.debug?.(`*** Found feature: ${JSON.stringify(feature)}`)
if (
!feature.discontinuousLocations ||
feature.discontinuousLocations.length === 0
) {
const errMsg =
'Must use "LocationEndChange" to change a feature end that does not have discontinuous locations'
logger.error(errMsg)
throw new Error(errMsg)
}
const oldEnd = feature.discontinuousLocations[index].end
if (oldEnd !== expectedOldEnd) {
const errMsg = `Location's current end value ${oldEnd} doesn't match with expected value ${expectedOldEnd}`
logger.error(errMsg)
throw new Error(errMsg)
}
const { start } = feature.discontinuousLocations[index]
if (newEnd <= start) {
const errMsg = `location end (${newEnd}) can't be smaller than location start (${start})`
logger.error(errMsg)
throw new Error(errMsg)
}
const nextLocation = feature.discontinuousLocations[index + 1]
if (nextLocation && newEnd >= nextLocation.start) {
const errMsg = `Location end (${newEnd}) can't be larger than the next location's start (${nextLocation.start})`
logger.error(errMsg)
throw new Error(errMsg)
}
feature.discontinuousLocations[index].end = newEnd
if (index === feature.discontinuousLocations.length - 1) {
feature.end = newEnd
}

try {
topLevelFeature.markModified('discontinuousLocations')
await topLevelFeature.save()
} catch (error) {
logger.debug?.(`*** FAILED: ${error}`)
throw error
}
}
}

async executeOnLocalGFF3(_backend: LocalGFF3DataStore) {
throw new Error('executeOnLocalGFF3 not implemented')
}

async executeOnClient(dataStore: ClientDataStore) {
if (!dataStore) {
throw new Error('No data store')
}
for (const [idx, changedId] of this.changedIds.entries()) {
const feature = dataStore.getFeature(changedId)
if (!feature) {
throw new Error(`Could not find feature with identifier "${changedId}"`)
}
const { index, newEnd } = this.changes[idx]
feature.setCDSDiscontinuousLocationEnd(newEnd, index)
}
}

getInverse() {
const { assembly, changedIds, changes, logger, typeName } = this
const inverseChangedIds = [...changedIds].reverse()
const inverseChanges = [...changes].reverse().map((change) => ({
featureId: change.featureId,
oldEnd: change.newEnd,
newEnd: change.oldEnd,
index: change.index,
}))
return new DiscontinuousLocationEndChange(
{
changedIds: inverseChangedIds,
typeName,
changes: inverseChanges,
assembly,
},
{ logger },
)
}
}

export function isDiscontinuousLocationEndChange(
change: unknown,
): change is DiscontinuousLocationEndChange {
return (
(change as DiscontinuousLocationEndChange).typeName ===
'DiscontinuousLocationEndChange'
)
}
Loading
Loading