wuffle
Version:
A multi-repository task board for GitHub issues
202 lines (165 loc) • 3.88 kB
JavaScript
export const LinkTypes = {
CLOSES: 'CLOSES',
CLOSED_BY: 'CLOSED_BY',
LINKED_TO: 'LINKED_TO',
LINKED_BY: 'LINKED_BY',
DEPENDS_ON: 'DEPENDS_ON',
REQUIRED_BY: 'REQUIRED_BY',
CHILD_OF: 'CHILD_OF',
PARENT_OF: 'PARENT_OF'
};
export const InverseLinkTypes = {
CLOSES: LinkTypes.CLOSED_BY,
CLOSED_BY: LinkTypes.CLOSES,
DEPENDS_ON: LinkTypes.REQUIRED_BY,
REQUIRED_BY: LinkTypes.DEPENDS_ON,
CHILD_OF: LinkTypes.PARENT_OF,
PARENT_OF: LinkTypes.CHILD_OF,
LINKED_TO: LinkTypes.LINKED_BY
};
/**
* @typedef { { sourceId: string, targetId: string, type: string, key: string } } Link
*/
/**
* A utility to maintain links
*/
export class Links {
constructor(data) {
this.links = (data && data.links) || {};
this.inverseLinks = (data && data.inverseLinks) || {};
}
/**
* @param {string} sourceId
* @param {string} targetId
* @param {string} linkType
* @param {Record<string,any>} [linkAttrs]
*
* @return {Link}
*/
createLink(sourceId, targetId, linkType, linkAttrs = {}) {
const key = `${targetId}-${linkType}`;
const link = {
...linkAttrs,
key,
sourceId,
targetId,
type: linkType
};
return link;
}
/**
* Establish a link between source and target with the given type.
*
* @param {Link} link
*/
addLink(link) {
const {
sourceId,
targetId,
type,
...linkAttrs
} = link;
if (!LinkTypes[type]) {
throw new Error(`unrecognized link type <${type}>`);
}
const links = this.links[sourceId] = (this.links[sourceId] || {});
const key = `${targetId}-${type}`;
links[key] = {
...linkAttrs,
key,
sourceId,
targetId,
type
};
const inverseLinkType = InverseLinkTypes[type];
if (inverseLinkType) {
const inverseLinks = this.inverseLinks[targetId] = (this.inverseLinks[targetId] || {});
const inverseKey = `${sourceId}-${inverseLinkType}`;
inverseLinks[inverseKey] = {
key: inverseKey,
sourceId: targetId,
targetId: sourceId,
type: inverseLinkType
};
}
}
/**
* Get all links that have the issue as the source.
*
* @param {string} sourceId
*
* @return {Record<string, Link>} links
*/
getBySource(sourceId) {
const links = this.getDirect(sourceId);
const inverseLinks = this.getInverse(sourceId);
return {
...links,
...inverseLinks
};
}
/**
* @param {string} sourceId
*
* @return {Record<string, Link>}
*/
getDirect(sourceId) {
return this.links[sourceId] || {};
}
/**
* @param {string} sourceId
*
* @return {Record<string, Link>}
*/
getInverse(sourceId) {
return this.inverseLinks[sourceId] || {};
}
/**
* Remove primary links that have the issue as the source.
*
* @param {string} sourceId
*
* @return {Link[]} removedLinks
*/
removeBySource(sourceId) {
const links = this.links[sourceId] || {};
for (const link of Object.values(links)) {
const {
targetId,
type
} = link;
const inverseLinkType = InverseLinkTypes[type];
if (inverseLinkType) {
const targetLinks = this.inverseLinks[targetId] || {};
delete targetLinks[`${sourceId}-${inverseLinkType}`];
}
}
this.links[sourceId] = {};
return links;
}
/**
* Serialize data to JSON so that it can
* later be loaded via #loadJSON.
*/
asJSON() {
const {
links,
inverseLinks
} = this;
return JSON.stringify({
links,
inverseLinks
});
}
/**
* Load a JSON object, previously serialized via Links#toJSON.
*/
loadJSON(json) {
const {
links,
inverseLinks
} = JSON.parse(json);
this.links = links || {};
this.inverseLinks = inverseLinks || {};
}
}