gitlab_service.js 7.3 KB
Newer Older
1
const vscode = require('vscode');
2
const request = require('request-promise');
P
Pierre Carru 已提交
3
const fs = require('fs');
4
const gitService = require('./git_service');
5
const openers = require('./openers');
6
const tokenService = require('./token_service');
F
Fatih Acet 已提交
7
const statusBar = require('./status_bar');
F
Fatih Acet 已提交
8 9

let branchMR = null;
10

F
Fatih Acet 已提交
11
async function fetch(path, method = 'GET', data = null) {
12
  const { instanceUrl, ignoreCertificateErrors, ca } = vscode.workspace.getConfiguration('gitlab');
13
  const { proxy } = vscode.workspace.getConfiguration('http');
14
  const apiRoot = `${instanceUrl}/api/v4`;
15
  const glToken = tokenService.getToken(instanceUrl);
16

F
Fatih Acet 已提交
17
  if (!glToken) {
F
Fatih Acet 已提交
18 19 20
    return vscode.window.showInformationMessage(
      'GitLab Workflow: Cannot make request. No token found.',
    );
F
Fatih Acet 已提交
21 22
  }

23 24
  const config = {
    url: `${apiRoot}${path}`,
F
Fatih Acet 已提交
25
    method,
26
    headers: {
F
Fatih Acet 已提交
27
      'PRIVATE-TOKEN': glToken,
F
Fatih Acet 已提交
28
    },
29
    rejectUnauthorized: !ignoreCertificateErrors,
30 31
  };

32
  if (proxy) {
33 34 35
    config.proxy = proxy;
  }

P
Pierre Carru 已提交
36 37 38 39 40 41 42 43
  if (ca) {
    try {
      config.ca = fs.readFileSync(ca);
    } catch (e) {
      vscode.window.showErrorMessage(`GitLab Workflow: Cannot read CA '${ca}'`);
    }
  }

F
Fatih Acet 已提交
44 45 46 47
  if (data) {
    config.formData = data;
  }

48 49 50 51 52
  const response = await request(config);

  try {
    return JSON.parse(response);
  } catch (e) {
53
    vscode.window.showInformationMessage('GitLab Workflow: Failed to perform your operation.');
F
Fatih Acet 已提交
54
    console.log('Failed to execute fetch', e);
55 56 57 58
    return { error: e };
  }
}

F
Fatih Acet 已提交
59 60 61 62 63 64 65 66 67 68 69 70 71
async function fetchCurrentProject() {
  const remote = await gitService.fetchGitRemote();

  if (remote) {
    const { namespace, project } = remote;
    const projectData = await fetch(`/projects/${namespace.replace(/\//g, '%2F')}%2F${project}`);

    return projectData || null;
  }

  return null;
}

F
Fatih Acet 已提交
72
async function fetchUser(userName) {
F
Fatih Acet 已提交
73 74
  let user = null;

75
  try {
F
Fatih Acet 已提交
76
    const path = userName ? `/user?search=${userName}` : '/user';
F
Fatih Acet 已提交
77

F
Fatih Acet 已提交
78
    user = await fetch(path);
79
  } catch (e) {
F
Fatih Acet 已提交
80
    let message = 'GitLab Workflow: GitLab user not found.';
F
Fatih Acet 已提交
81 82 83 84 85 86

    if (!userName) {
      message += ' Check your Personal Access Token.';
    }

    vscode.window.showInformationMessage(message);
87
  }
F
Fatih Acet 已提交
88 89

  return user;
90 91
}

F
Fatih Acet 已提交
92
async function fetchIssuables(params = {}) {
93 94
  let project = null;
  let issuables = [];
F
Fatih Acet 已提交
95 96 97 98 99 100
  const { type, scope, state } = params;
  const config = {
    type: type || 'merge_requests',
    scope: scope || 'created-by-me',
    state: state || 'opened',
  }
101 102 103 104 105 106

  try {
    project = await fetchCurrentProject();
  } catch (e) {
    // Fail silently
  }
F
Fatih Acet 已提交
107 108

  if (project) {
F
Fatih Acet 已提交
109 110
    const path = `/projects/${project.id}/${config.type}?scope=${config.scope}&state=${config.state}`;
    issuables = await fetch(path);
F
Fatih Acet 已提交
111 112
  }

F
Fatih Acet 已提交
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
  return issuables;
}

async function fetchIssuesAssignedToMe() {
  return await fetchIssuables({
    type: 'issues',
    scope: 'assigned_to_me',
  });
}

async function fetchIssuesCreatedByMe() {
  return await fetchIssuables({
    type: 'issues',
    scope: 'created_by_me',
  });
}

async function fetchMergeRequestsAssignedToMe() {
  return await fetchIssuables({
    scope: 'assigned-to-me',
  });
}

async function fetchMergeRequestsCreatedByMe() {
  return await fetchIssuables();
}

async function fetchMyOpenMergeRequests() {
  return await fetchIssuables();
142 143 144
}

async function fetchLastPipelineForCurrentBranch() {
F
Fatih Acet 已提交
145
  const project = await fetchCurrentProject();
F
Fatih Acet 已提交
146
  let pipeline = null;
147

F
Fatih Acet 已提交
148 149 150 151
  if (project) {
    const branchName = await gitService.fetchTrackingBranchName();
    const pipelinesRootPath = `/projects/${project.id}/pipelines`;
    const pipelines = await fetch(`${pipelinesRootPath}?ref=${branchName}`);
152 153

    if (pipelines.length) {
F
Fatih Acet 已提交
154
      pipeline = await fetch(`${pipelinesRootPath}/${pipelines[0].id}`);
155
    }
F
Fatih Acet 已提交
156 157
  }

F
Fatih Acet 已提交
158
  return pipeline;
F
Fatih Acet 已提交
159 160
}

F
Fatih Acet 已提交
161 162 163 164 165
/**
 * GitLab API doesn't support getting open MR by commit ID or branch name.
 * Using this recursive fetcher method, we fetch 100 MRs at a time and do pagination
 * until we find the MR for current branch. This method will retry max 5 times.
 */
F
Fatih Acet 已提交
166
async function fetchOpenMergeRequestForCurrentBranch() {
F
Fatih Acet 已提交
167 168 169 170
  if (branchMR) {
    return branchMR;
  }

F
Fatih Acet 已提交
171 172 173 174
  const project = await fetchCurrentProject();
  const branchName = await gitService.fetchTrackingBranchName();
  let page = 1;

F
Fatih Acet 已提交
175
  // Recursive fetcher method to find the branch MR in MR list.
F
Fatih Acet 已提交
176
  async function fetcher() {
F
Fatih Acet 已提交
177 178 179
    const path = `/projects/${project.id}/merge_requests?state=opened&per_page=100&page=${page}`;
    const mrs = await fetch(path);
    const [mr] = mrs.filter(m => m.source_branch === branchName);
F
Fatih Acet 已提交
180 181

    if (mr) {
F
Fatih Acet 已提交
182 183
      if (page > 1) {
        // Cache only if we need to do pagination.
F
Fatih Acet 已提交
184 185 186
        branchMR = mr;
      }

F
Fatih Acet 已提交
187 188 189
      return mr;
    }

F
Fatih Acet 已提交
190 191 192
    if (page <= 5 && mrs.length === 100) {
      // Retry max 5 times.
      page += 1;
F
Fatih Acet 已提交
193 194
      return await fetcher();
    }
F
Fatih Acet 已提交
195 196

    return null;
F
Fatih Acet 已提交
197 198 199 200 201
  }

  return project ? await fetcher() : null;
}

F
Fatih Acet 已提交
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
/**
 * Cancels or retries last pipeline or creates a new pipeline for current branch.
 *
 * @param {string} action create|retry|cancel
 */
async function handlePipelineAction(action) {
  const pipeline = await fetchLastPipelineForCurrentBranch();
  const project = await fetchCurrentProject();

  if (pipeline && project) {
    let endpoint = `/projects/${project.id}/pipelines/${pipeline.id}/${action}`;
    let newPipeline = null;

    if (action === 'create') {
      const branchName = await gitService.fetchTrackingBranchName();
      endpoint = `/projects/${project.id}/pipeline?ref=${branchName}`;
    }

    try {
      newPipeline = await fetch(endpoint, 'POST');
    } catch (e) {
      vscode.window.showErrorMessage(`GitLab Workflow: Failed to ${action} pipeline.`);
    }

    if (newPipeline) {
227
      openers.openUrl(`${project.web_url}/pipelines/${newPipeline.id}`);
F
Fatih Acet 已提交
228
      statusBar.refreshPipeline();
F
Fatih Acet 已提交
229 230 231 232 233 234
    }
  } else {
    vscode.window.showErrorMessage('GitLab Workflow: No project or pipeline found.');
  }
}

F
Fatih Acet 已提交
235 236 237 238 239 240 241 242
async function fetchMRIssues(mrId) {
  const project = await fetchCurrentProject();
  let issues = [];

  if (project) {
    try {
      issues = await fetch(`/projects/${project.id}/merge_requests/${mrId}/closes_issues`);
    } catch (e) {
F
Fatih Acet 已提交
243
      console.log('Failed to execute fetchMRIssue', e);
F
Fatih Acet 已提交
244 245 246 247
    }
  }

  return issues;
F
Fatih Acet 已提交
248
}
F
Fatih Acet 已提交
249

F
Fatih Acet 已提交
250 251 252 253 254 255 256 257 258 259
async function createSnippet(data) {
  let snippet;

  try {
    snippet = await fetch(`/projects/${data.id}/snippets`, 'POST', data);
  } catch (e) {
    vscode.window.showInformationMessage('GitLab Workflow: Failed to create your snippet.');
  }

  return snippet;
F
Fatih Acet 已提交
260
}
F
Fatih Acet 已提交
261

F
Fatih Acet 已提交
262 263 264 265 266 267 268 269 270 271 272 273
async function validateCIConfig(content) {
  let response = null;

  try {
    response = await fetch('/ci/lint', 'POST', { content });
  } catch (e) {
    vscode.window.showInformationMessage('GitLab Workflow: Failed to validate CI configuration.');
  }

  return response;
}

274
exports.fetchUser = fetchUser;
F
Fatih Acet 已提交
275 276 277 278
exports.fetchIssuesAssignedToMe = fetchIssuesAssignedToMe;
exports.fetchIssuesCreatedByMe = fetchIssuesCreatedByMe;
exports.fetchMergeRequestsAssignedToMe = fetchMergeRequestsAssignedToMe;
exports.fetchMergeRequestsCreatedByMe = fetchMergeRequestsCreatedByMe;
279 280 281
exports.fetchMyOpenMergeRequests = fetchMyOpenMergeRequests;
exports.fetchOpenMergeRequestForCurrentBranch = fetchOpenMergeRequestForCurrentBranch;
exports.fetchLastPipelineForCurrentBranch = fetchLastPipelineForCurrentBranch;
F
Fatih Acet 已提交
282
exports.fetchCurrentProject = fetchCurrentProject;
F
Fatih Acet 已提交
283
exports.handlePipelineAction = handlePipelineAction;
F
Fatih Acet 已提交
284
exports.fetchMRIssues = fetchMRIssues;
F
Fatih Acet 已提交
285
exports.createSnippet = createSnippet;
F
Fatih Acet 已提交
286
exports.validateCIConfig = validateCIConfig;