grigori-gru/jira-to-matrix

View on GitHub
src/task-trackers/gitlab/parser.gtilab.ts

Summary

Maintainability
C
1 day
Test Coverage
import {
    Parser,
    Config,
    CreateRoomData,
    PostCommentData,
    InviteNewMembersData,
    PostIssueUpdatesData,
    UploadData,
    RoomViewStateEnum,
    PushCommitData,
    ActionNames,
    PostPipelineData,
    PostMilestoneUpdatesData,
    MilestoneUpdateStatus,
} from '../../types';
import {
    GitlabSelectors,
    HookTypes,
    GitlabPushHook,
    GitlabPipelineHook,
    PipelineBuild,
    GitlabPipeline,
    successStatus,
} from './types';
import Lo from 'lodash/fp';

export class GitlabParser implements Parser {
    issueMovedType = 'issueMovedType';

    constructor(private features: Config['features'], private selectors: GitlabSelectors) {}

    isPostPipeline(body: any) {
        return Boolean(this.features.postIssueUpdates && this.selectors.isPipelineHook(body));
    }

    // private isSuccessAttributes = (
    //     attrributes: Omit<SuccessAttributes, 'status'> & { status: string },
    // ): attrributes is SuccessAttributes => successStatus.some(el => el === attrributes.status);
    static stageFilter = object =>
        Object.entries(object).reduce((acc, [key, value]) => {
            if (Array.isArray(value) && value.length) {
                acc[key] = value;
            }
            return acc;
        }, {});

    private getPipelineData = (body: GitlabPipelineHook): GitlabPipeline => {
        const baseAtributes = {
            url: body.project.web_url + '/pipelines/' + body.object_attributes.id,
            username: body.user.username,
            sha: body.object_attributes.sha,
        };

        if (successStatus.some(el => el === body.object_attributes.status)) {
            return baseAtributes;
        }

        const groupedBuilds = Lo.pipe(
            Lo.path('builds'),
            Lo.groupBy('stage'),
            Lo.mapValues(
                Lo.pipe(
                    Lo.filter((el: PipelineBuild) => el.status !== 'success' && el.status !== 'skipped'),
                    Lo.map((el: PipelineBuild) => ({ [el.name]: el.status })),
                ),
            ),
            GitlabParser.stageFilter,
        )(body);

        const stages = body.object_attributes.stages
            .filter(el => groupedBuilds[el])
            .map(el => ({ [el]: groupedBuilds[el] })) as any;

        const failOutput = {
            ...baseAtributes,
            username: body.user.username,
            sha: body.object_attributes.sha,
            stages: stages,
        };

        return failOutput;
    };
    static getHeader = (project: string, ref: string, status: string) => `${project} (${ref}): ${status}`;

    getPostPipelineData(body: GitlabPipelineHook): PostPipelineData {
        const issueKeys = this.selectors.getPostKeys(body);
        const pipelineData: PostPipelineData['pipelineData'] = issueKeys.map(key => {
            const keyData = this.selectors.transformFromIssueKey(key);
            const repoName = keyData.namespaceWithProject.split('/').reverse()[0];
            return {
                header: GitlabParser.getHeader(repoName, body.object_attributes.ref, body.object_attributes.status),
                key: key,
                pipeInfo: this.getPipelineData(body),
            };
        });

        return {
            author: this.selectors.getFullNameWithId(body),
            pipelineData,
        };
    }

    isPostPushCommit(body) {
        return Boolean(this.features.postIssueUpdates && this.selectors.isCorrectWebhook(body, HookTypes.Push));
    }

    getPostPushCommitData(body: GitlabPushHook): PushCommitData {
        const author = this.selectors.getFullNameWithId(body);
        const keyAndCommits = this.selectors.getCommitKeysBody(body);

        return {
            // projectNamespace: this.selectors.getProjectKey(body)!,
            author,
            keyAndCommits,
        };
    }

    isPostIssueUpdates(body) {
        return Boolean(
            (this.features.postIssueUpdates &&
                this.selectors.getIssueChanges(body) &&
                this.selectors.isCorrectWebhook(body, 'update')) ||
                this.selectors.isCorrectWebhook(body, 'close') ||
                this.selectors.isCorrectWebhook(body, 'reopen'),
        );
    }

    getPostIssueUpdatesData(body): PostIssueUpdatesData {
        const author = this.selectors.getDisplayName(body)!;
        const changes = this.selectors.getIssueChanges(body)!;
        const hookLabels = this.selectors.getIssueLabels(body);
        const newTitleData = changes.find(data => data.field === 'title');
        const newMilestone = changes.find(data => data.field === 'milestone_id');
        const oldKey = this.selectors.getIssueKey(body);
        let newRoomName: string | undefined;

        if (newMilestone) {
            newRoomName = this.selectors.composeRoomName(oldKey, {
                summary: this.selectors.getSummary(body)!,
                milestone: newMilestone.newValue,
            });
        }
        if (newTitleData) {
            newRoomName = this.selectors.composeRoomName(oldKey, {
                summary: newTitleData.newValue,
            });
        }
        if (this.selectors.isCorrectWebhook(body, 'close')) {
            newRoomName = this.selectors.composeRoomName(oldKey, {
                summary: this.selectors.getSummary(body)!,
                state: RoomViewStateEnum.close,
            });
        }
        if (this.selectors.isCorrectWebhook(body, 'reopen')) {
            newRoomName = this.selectors.composeRoomName(oldKey, {
                summary: this.selectors.getSummary(body)!,
                state: RoomViewStateEnum.open,
            });
        }
        const isNewStatus = Boolean(changes.find(data => data.field === 'labels' || data.field === 'status'));

        const projectKey = this.selectors.getProjectKey(body)!;

        return { oldKey, changes, author, projectKey, newRoomName, hookLabels, isNewStatus };
    }

    isCreateRoom(body) {
        return Boolean(
            this.features.createRoom &&
                !this.selectors.isCorrectWebhook(body, HookTypes.Push) &&
                !this.selectors.isCorrectWebhook(body, HookTypes.Pipeline) &&
                this.selectors.getKey(body),
            // TODO not found webhook for issue moved to another project
            // && this.selectors.getTypeEvent(body) !== this.issueMovedType,
        );
    }

    getCreateRoomData(body): CreateRoomData {
        const projectKey = this.selectors.getProjectKey(body);
        const summary = this.selectors.getSummary(body);
        const key = this.selectors.getIssueKey(body);
        const hookLabels = this.selectors.getIssueLabels(body);
        const descriptionFields = this.selectors.getDescriptionFields(body);
        const milestoneId = this.selectors.getMilestoneId(body) || undefined;

        const parsedIssue = { key, summary, projectKey, descriptionFields, hookLabels };

        return {
            issue: parsedIssue,
            projectKey: this.features.createProjectRoom ? projectKey : undefined,
            milestoneId,
        };
    }

    getPostCommentData(body): PostCommentData {
        const headerText = this.selectors.getHeaderText(body)!;
        const author = this.selectors.getDisplayName(body)!;
        const issueId = this.selectors.getIssueKey(body);
        const comment = this.selectors.getCommentBody(body);

        return { issueId, headerText, comment, author };
    }

    isPostComment(body) {
        return Boolean(
            this.features.postComments && this.selectors.isCommentEvent(body) && !this.selectors.isUploadBody(body),
        );
    }

    isPostMilestoneUpdates(body) {
        const isMilestoneIssue =
            this.selectors.getMilestoneId(body) ||
            this.selectors.getIssueChanges(body)?.some(el => el.field === 'milestone_id');

        return Boolean(this.features.postMilestoneUpdates && isMilestoneIssue);
    }

    getPostMilestoneUpdatesData(body): PostMilestoneUpdatesData {
        // TODO milestone add deleting from removed milestone
        // const isMilestoneUpdated = data => {
        //     const changes = this.selectors.getIssueChanges(data);
        //     if (changes) {
        //         const newMilestone = changes.find(el => el.field === 'milestone_id');

        //         const res = newMilestone && !newMilestone.newValue;

        //         return Boolean(res);
        //     }

        //     return false;
        // };

        const isMilestoneDeleted = data => {
            const changes = this.selectors.getIssueChanges(data);
            if (changes) {
                const newMilestone = changes.find(el => el.field === 'milestone_id');

                const res = newMilestone && !newMilestone.newValue;

                return Boolean(res);
            }

            return false;
        };

        const status = Lo.cond([
            [isMilestoneDeleted, Lo.always(MilestoneUpdateStatus.Deleted)],
            [el => this.selectors.isCorrectWebhook(el, 'close'), Lo.always(MilestoneUpdateStatus.Closed)],
            [el => this.selectors.isCorrectWebhook(el, 'reopen'), Lo.always(MilestoneUpdateStatus.Reopen)],
            [Lo.T, Lo.always(MilestoneUpdateStatus.Created)],
        ])(body);

        return {
            issueKey: this.selectors.getIssueKey(body),
            milestoneId:
                status === MilestoneUpdateStatus.Deleted
                    ? body?.changes?.milestone_id?.previous
                    : this.selectors.getMilestoneId(body)!,
            summary: this.selectors.getSummary(body)!,
            user: this.selectors.getDisplayName(body)!,
            status,
        };
    }

    getInviteNewMembersData(body): InviteNewMembersData {
        const key = this.selectors.getIssueKey(body);
        const descriptionFields = this.selectors.getDescriptionFields(body);
        const projectKey = this.selectors.getProjectKey(body);

        return { key, typeName: descriptionFields?.typeName, projectKey };
    }

    getUploadData(body): UploadData {
        return {
            uploadInfo: this.selectors.getUploadInfo(body)!,
            issueKey: this.selectors.getIssueKey(body),
            uploadUrls: this.selectors.getUploadUrl(body)!,
        };
    }

    isMemberInvite(body) {
        return (
            this.features.inviteNewMembers &&
            (this.selectors.isCorrectWebhook(body, 'update') || this.selectors.isCorrectWebhook(body, 'reopen'))
        );
    }

    isUpload(body) {
        return this.features.postComments && this.selectors.isCommentEvent(body) && this.selectors.isUploadBody(body);
    }

    actionFuncs = {
        [ActionNames.PostComment]: this.isPostComment,
        [ActionNames.InviteNewMembers]: this.isMemberInvite,
        [ActionNames.PostIssueUpdates]: this.isPostIssueUpdates,
        [ActionNames.Upload]: this.isUpload,
        [ActionNames.PostCommit]: this.isPostPushCommit,
        [ActionNames.Pipeline]: this.isPostPipeline,
        [ActionNames.PostMilestoneUpdates]: this.isPostMilestoneUpdates,
    };

    getBotActions(body) {
        return Object.keys(this.actionFuncs).filter(key => this.actionFuncs[key].bind(this)(body));
    }
}