com.microsoft.alm.plugin.idea.common.ui.workitem.VcsWorkItemsModel.java Source code

Java tutorial

Introduction

Here is the source code for com.microsoft.alm.plugin.idea.common.ui.workitem.VcsWorkItemsModel.java

Source

// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root.

package com.microsoft.alm.plugin.idea.common.ui.workitem;

import com.google.common.annotations.VisibleForTesting;
import com.intellij.ide.BrowserUtil;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationListener;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.VcsNotifier;
import com.microsoft.alm.common.artifact.GitRefArtifactID;
import com.microsoft.alm.common.utils.UrlHelper;
import com.microsoft.alm.plugin.authentication.AuthHelper;
import com.microsoft.alm.plugin.context.RepositoryContext;
import com.microsoft.alm.plugin.context.ServerContext;
import com.microsoft.alm.plugin.context.ServerContextManager;
import com.microsoft.alm.plugin.idea.common.resources.TfPluginBundle;
import com.microsoft.alm.plugin.idea.common.ui.common.tabs.TabModelImpl;
import com.microsoft.alm.plugin.idea.common.utils.EventContextHelper;
import com.microsoft.alm.plugin.idea.common.utils.VcsHelper;
import com.microsoft.alm.plugin.idea.git.ui.branch.CreateBranchController;
import com.microsoft.alm.plugin.operations.Operation;
import com.microsoft.alm.plugin.operations.OperationExecutor;
import com.microsoft.alm.plugin.operations.WorkItemLookupOperation;
import com.microsoft.alm.plugin.telemetry.TfsTelemetryHelper;
import com.microsoft.alm.workitemtracking.webapi.models.Link;
import com.microsoft.alm.workitemtracking.webapi.models.WorkItem;
import com.microsoft.visualstudio.services.webapi.patch.json.JsonPatchDocument;
import com.microsoft.visualstudio.services.webapi.patch.json.JsonPatchOperation;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.event.HyperlinkEvent;
import java.net.URI;
import java.util.HashMap;
import java.util.List;

public class VcsWorkItemsModel extends TabModelImpl<WorkItemsTableModel> {
    private static final Logger logger = LoggerFactory.getLogger(VcsWorkItemsModel.class);

    private static final String DEFAULT_BRANCH_NAME_PATTERN = "workitem%s";
    private static final String LINK_NAME_KEY = "name";
    private static final String LINK_NAME_VALUE = "Branch";
    private static final String ARTIFACT_LINK_RELATION = "ArtifactLink";
    private static final String RELATIONS_PATH = "/relations/-";
    public static final String ASSOCIATE_WORK_ITEM_ACTION = "associate-work-item";
    public static final String CONTEXT_FOUND = "context";
    private boolean isContextFound = false;

    public VcsWorkItemsModel(final @NotNull Project project) {
        super(project, new WorkItemsTableModel(WorkItemsTableModel.COLUMNS_PLUS_BRANCH), "WorkItemsTab.");
        operationInputs = new WorkItemLookupOperation.WitInputs(WorkItemHelper.getAssignedToMeQuery());
    }

    @VisibleForTesting
    protected VcsWorkItemsModel(final @NotNull Project project, final @NotNull WorkItemsTableModel tableModel) {
        super(project, tableModel, "WorkItemsTab.");
    }

    protected void createDataProvider() {
        dataProvider = new WorkItemsTabLookupListener(this);
    }

    public void setContextFound(final boolean isFound) {
        // notify if context is found for the first time
        if (isContextFound != isFound && isFound) {
            logger.info("Context found for work items tab for first time");
            isContextFound = isFound;
            setChangedAndNotify(CONTEXT_FOUND);
        } else if (isContextFound != isFound) {
            logger.warn("Context became invalid and isn't known anymore");
            isContextFound = isFound;
        }
    }

    public void openGitRepoLink() {
        // create a new work item and open WIT takes you to the same place at the moment
        createNewItem();
    }

    public void openSelectedItemsLink() {
        if (isTeamServicesRepository()) {
            final ServerContext context = ServerContextManager.getInstance().get(repositoryContext.getUrl());

            if (context != null) {
                final URI teamProjectURI = context.getTeamProjectURI();
                if (teamProjectURI != null) {
                    final List<WorkItem> workItems = viewForModel.getSelectedWorkItems();
                    for (WorkItem item : workItems) {
                        super.gotoLink(UrlHelper.getSpecificWorkItemURI(teamProjectURI, item.getId()).toString());
                    }
                } else {
                    logger.warn(
                            "Can't goto 'create work item' link: Unable to get team project URI from server context.");
                }
            }
        }
    }

    public void createBranch() {
        if (!isTeamServicesRepository() || repositoryContext.getType() != RepositoryContext.Type.GIT) {
            logger.debug("createBranch: cannot associate a work item with a branch in a non-TF Git repo");
            return;
        }

        final ServerContext context = ServerContextManager.getInstance().get(repositoryContext.getUrl());
        final WorkItem workItem = viewForModel.getSelectedWorkItems().get(0); // TODO: associate multiple work items with a branch

        // call the Create Branch dialog and get the branch name from the user
        final CreateBranchController controller = new CreateBranchController(project,
                String.format(DEFAULT_BRANCH_NAME_PATTERN, workItem.getId()), VcsHelper.getGitRepository(project));

        if (controller.showModalDialog()) {
            final String branchName = controller.getBranchName();
            try {
                //TODO should this be an IntelliJ background task so we can provide progress information? (if so we should pass the progress indicator to createBranch and create association)
                OperationExecutor.getInstance().submitOperationTask(new Runnable() {
                    @Override
                    public void run() {
                        // do branch creation
                        final boolean wasBranchCreated = controller.createBranch(context);

                        // check if branch creation succeeded before associating the work item to it
                        boolean wasWorkItemAssociated = false;
                        if (wasBranchCreated) {
                            wasWorkItemAssociated = createWorkItemBranchAssociation(context, branchName,
                                    workItem.getId());

                            logger.info(
                                    "Work item association " + (wasWorkItemAssociated ? "succeeded" : "failed"));
                            final String notificationMsg = TfPluginBundle.message(
                                    wasWorkItemAssociated
                                            ? TfPluginBundle.KEY_WIT_ASSOCIATION_SUCCESSFUL_DESCRIPTION
                                            : TfPluginBundle.KEY_WIT_ASSOCIATION_FAILED_DESCRIPTION,
                                    UrlHelper.getSpecificWorkItemURI(context.getTeamProjectURI(), workItem.getId()),
                                    workItem.getId(), UrlHelper.getBranchURI(context.getUri(), branchName),
                                    branchName);

                            VcsNotifier.getInstance(project).notifyImportantInfo(
                                    TfPluginBundle.message(wasWorkItemAssociated
                                            ? TfPluginBundle.KEY_WIT_ASSOCIATION_SUCCESSFUL_TITLE
                                            : TfPluginBundle.KEY_WIT_ASSOCIATION_FAILED_TITLE),
                                    notificationMsg, new NotificationListener() {
                                        @Override
                                        public void hyperlinkUpdate(@NotNull Notification notification,
                                                @NotNull HyperlinkEvent hyperlinkEvent) {
                                            BrowserUtil.browse(hyperlinkEvent.getURL());
                                        }
                                    });
                        }

                        TfsTelemetryHelper.getInstance().sendEvent(ASSOCIATE_WORK_ITEM_ACTION,
                                new TfsTelemetryHelper.PropertyMapBuilder().currentOrActiveContext(context)
                                        .actionName(ASSOCIATE_WORK_ITEM_ACTION).success(wasWorkItemAssociated)
                                        .build());

                        // Update the work items tab and any other listener to WorkItemChanged events
                        EventContextHelper.triggerWorkItemChanged(EventContextHelper.SENDER_ASSOCIATE_BRANCH,
                                project);
                    }
                });
            } catch (Exception e) {
                logger.error("Failed to create a branch and associate it with a work item", e);
            }
        }
    }

    protected boolean createWorkItemBranchAssociation(final ServerContext context, final String branchName,
            final int workItemId) {
        final GitRefArtifactID gitRefArtifactID = new GitRefArtifactID(
                context.getTeamProjectReference().getId().toString(), context.getGitRepository().getId().toString(),
                branchName);

        // attributes specify the link type
        final HashMap<String, Object> attributes = new HashMap<String, Object>();
        attributes.put(LINK_NAME_KEY, LINK_NAME_VALUE);

        // create link object to add to the work item
        final Link link = new Link();
        link.setUrl(gitRefArtifactID.encodeURI());
        link.setTitle(StringUtils.EMPTY);
        link.setRel(ARTIFACT_LINK_RELATION);
        link.setAttributes(attributes);

        // create the operation that will add the link to the work item
        final JsonPatchOperation operation = new JsonPatchOperation();
        operation.setOp(com.microsoft.visualstudio.services.webapi.patch.Operation.ADD);
        operation.setPath(RELATIONS_PATH);
        operation.setValue(link);

        final JsonPatchDocument doc = new JsonPatchDocument();
        doc.add(operation);

        try {
            context.getWitHttpClient().updateWorkItem(doc, workItemId, false, false);
            return true;
        } catch (Throwable t) {
            if (AuthHelper.isNotAuthorizedError(t)) {
                final ServerContext newContext = ServerContextManager.getInstance()
                        .updateAuthenticationInfo(context.getGitRepository().getRemoteUrl());
                if (newContext != null) {
                    //retry creating the branch with new context and authentication info
                    return createWorkItemBranchAssociation(newContext, branchName, workItemId);
                } else {
                    logger.error(
                            "createWorkItemBranchAssociation isNotAuthorizedError and failed to create a new context to use");
                    return false;
                }
            } else {
                logger.error(
                        "createWorkItemBranchAssociation experienced an exception while associating a work item and branch",
                        t);
                return false;
            }
        }
    }

    public void appendData(final Operation.Results results) {
        final WorkItemLookupOperation.WitResults witResults = (WorkItemLookupOperation.WitResults) results;
        viewForModel.addWorkItems(witResults.getWorkItems());
    }

    public void clearData() {
        viewForModel.clearRows();
    }

    public void createNewItem() {
        if (isTeamServicesRepository()) {
            final ServerContext context = ServerContextManager.getInstance().get(repositoryContext.getUrl());

            if (context != null) {
                final URI teamProjectURI = context.getTeamProjectURI();
                if (teamProjectURI != null) {
                    super.gotoLink(UrlHelper.getCreateWorkItemURI(teamProjectURI).toString());
                } else {
                    logger.warn(
                            "Can't goto 'create work item' link: Unable to get team project URI from server context.");
                }
            }
        }
    }
}