drupalorg-1.0.x-dev/src/Controller/IssueForksController.php

src/Controller/IssueForksController.php
<?php

namespace Drupal\drupalorg\Controller;

use Drupal\Component\Utility\Html;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Link;
use Drupal\Core\Queue\QueueFactory;
use Drupal\Core\Render\Renderer;
use Drupal\drupalorg\Traits\GitLabClientTrait;
use Drupal\drupalorg\UserService;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

/**
 * Controller to manage issue forks, merge requests, etc.
 */
class IssueForksController extends ControllerBase {

  use GitLabClientTrait;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('queue'),
      $container->get('drupalorg.user_service'),
      $container->get('renderer')
    );
  }

  /**
   * Construct method.
   *
   * @param \Drupal\Core\Queue\QueueFactory $queueFactory
   *   Queue factory.
   * @param \Drupal\drupalorg\UserService $userService
   *   User service from drupalorg module.
   * @param \Drupal\Core\Render\Renderer $renderer
   *   Renderer service.
   */
  public function __construct(
    protected QueueFactory $queueFactory,
    protected UserService $userService,
    protected Renderer $renderer,
  ) {
  }

  /**
   * Check branches of an issue fork.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The current request.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   Branches information.
   */
  public function issueForkCheckBranches(Request $request): JsonResponse {
    $fork_id_param = $request->query->get('fork_id');
    $source_link_param = $request->query->get('source_link');
    $existing_branches_param = $request->query->all('existing_branches');
    $data = [
      'status' => FALSE,
      'fork_id' => $fork_id_param,
      'branches' => [],
      'new_branches' => FALSE,
      'message' => '',
    ];

    if (empty($fork_id_param) || empty($source_link_param)) {
      $data['error'] = '"fork_id" and "source_link" are required';
      return new JsonResponse($data);
    }

    [
      'project' => $project_id,
      'issue_iid' => $issue_iid,
      'error' => $error,
    ] = $this->getProjectAndIssueIdFromUrl($source_link_param);

    try {
      // Get issue specific branches.
      $issue_specific_branches = $this->getGitLabClient()->repositories()->branches($fork_id_param, [
        'search' => '^' . $issue_iid . '-',
      ]);
      $data['branches'] = $issue_specific_branches;
      if (!empty($issue_specific_branches)) {
        $issue_specific_branches_names = array_map(fn($i) => $i['name'], $issue_specific_branches);
        // rsort($existing_branches_param);
        // Compare existing with newly returned to see if there are new ones.
        $data['new_branches'] = !empty(array_diff($issue_specific_branches_names, $existing_branches_param));
      }
      $data['status'] = TRUE;
    }
    catch (\Throwable $e) {
      $data['message'] = 'Could not check branches.';
      $this->getLogger('drupalorg')->error('Error checking branches in fork. Message: @message', [
        '@message' => $e->getMessage(),
      ]);
    }

    return new JsonResponse($data);
  }

  /**
   * Check access to an issue fork.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The current request.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   Whether the user has access to the fork or not in JSON format.
   */
  public function issueForkCheckAccess(Request $request): JsonResponse {
    $fork_id_param = $request->query->get('fork_id');
    $data = [
      'access' => FALSE,
      'fork_id' => $fork_id_param,
      'message' => '',
    ];

    if (empty($fork_id_param)) {
      $data['error'] = '"fork_id" is required';
      return new JsonResponse($data);
    }

    $gitlab_user_id = $this->userService->getGitLabUserId($this->currentUser());
    if (empty($gitlab_user_id)) {
      $data['error'] = 'You need to set up your git username in your profile';
      return new JsonResponse($data);
    }

    try {
      $member = $this->getGitLabClient()->projects()->member($fork_id_param, $gitlab_user_id);
      $data['access'] = !empty($member);
    }
    catch (\Throwable $e) {
      $data['message'] = 'Not allowed or fork not found';
      $this->getLogger('drupalorg')->error('Error checking member in fork. Message: @message', [
        '@message' => $e->getMessage(),
      ]);
    }

    return new JsonResponse($data);
  }

  /**
   * Grant access to an issue fork.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The current request.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse|\Symfony\Component\HttpFoundation\RedirectResponse
   *   Whether the user has access to the fork or not in JSON format.
   */
  public function issueForkRequestAccess(Request $request): JsonResponse|RedirectResponse {
    $format_param = $request->query->get('format');
    $fork_id_param = $request->query->get('fork_id');
    $source_link_param = $request->query->get('source_link');
    $data = [
      'access' => FALSE,
      'fork_id' => $fork_id_param,
      'message' => '',
    ];

    if (empty($fork_id_param)) {
      if ($format_param === 'json') {
        $data['error'] = '"fork_id" is required';
        return new JsonResponse($data);
      }
      $this->messenger()->addError($this->t('"fork_id" is required'));
      return $this->redirect('drupalorg.issue_fork_management', [], [
        'query' => [
          'source_link' => $source_link_param,
        ],
      ]);
    }

    $gitlab_user_id = $this->userService->getGitLabUserId($this->currentUser());
    if (empty($gitlab_user_id)) {
      $data['error'] = 'You need to set up your git username in your profile';
      return new JsonResponse($data);
    }

    if ($this->addUserToFork($fork_id_param, $gitlab_user_id)) {
      $data['access'] = TRUE;
    }
    else {
      $data['message'] = $this->t('Could not grant access. Try again later or review your git username.');
    }

    if ($format_param === 'json') {
      return new JsonResponse($data);
    }

    // Non-json. Show messages and do redirects instead.
    if ($data['access']) {
      $this->messenger()->addStatus($this->t('Access granted'));
    }
    else {
      $this->messenger()->addError($data['message']);
    }
    return $this->redirect('drupalorg.issue_fork_management', [], [
      'query' => [
        'source_link' => $source_link_param,
      ],
    ]);
  }

  /**
   * Creates a new fork.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The current request.
   *
   * @return array|\Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\JsonResponse
   *   Render array or redirect.
   */
  public function issueForkCreateFork(Request $request): JsonResponse | RedirectResponse | array {
    $format_param = $request->query->get('format');
    $with_branch_param = $request->query->get('with_branch');
    $branch_name_param = $request->query->get('branch_name');
    $source_branch_param = $request->query->get('source_branch');
    $source_link_param = $request->query->get('source_link');
    [
      'project' => $project_id,
      'issue_iid' => $issue_iid,
      'error' => $error,
    ] = $this->getProjectAndIssueIdFromUrl($source_link_param);

    $data = [
      'created' => FALSE,
      'fork' => NULL,
      'message' => '',
    ];

    if (!empty($error)) {
      if ($format_param === 'json') {
        $data['message'] = $this->t('Something went wrong');
        return new JsonResponse($data);
      }
      $this->messenger()->addError($error);
      return [
        '#title' => $this->t('Forks management'),
        '#markup' => $this->t('Something went wrong.'),
      ];
    }

    // Make source that the branch name is correct.
    $branch_name_param = !empty($branch_name_param) ?
      substr(trim(Html::getUniqueId($branch_name_param), '-'), 0, 50) :
      'issue-branch';
    $branch_name = $with_branch_param ? $issue_iid . '-' . $branch_name_param : NULL;
    $gitlab_user_id = $this->userService->getGitLabUserId($this->currentUser());
    $fork_created = NULL;
    try {
      $fork = $this->createFork($source_link_param, $gitlab_user_id);
      if (!empty($fork['name'])) {
        // Update the issue with a link for fork management.
        $this->getGitLabClient()->issues()->addNote(
          $project_id,
          $issue_iid,
          $this->automatedNoteHeading($this->t('Fork created:')) . $this->t("A [fork](@fork) was created for this issue. Go to the @link page for this issue to find the git commands to checkout a branch on this fork. React to this message with :heavy_plus_sign: to gain access.", [
            '@fork' => $fork['web_url'],
            '@link' => Link::createFromRoute(t('fork management'), 'drupalorg.issue_fork_management', [],
              [
                'query' => ['source_link' => $source_link_param],
                'absolute' => TRUE,
              ]
            )->toString(),
          ])
        );

        // This part is not relevant to creating the fork, so queue it.
        $this->queueFactory->get('drupalorg_issue_forks_queue_worker')->createItem([
          'action' => 'post_fork_creation',
          'fork_id' => $fork['id'],
          'user_id' => $this->currentUser()->id(),
          'issue_id' => $issue_iid,
          'issue_link' => $source_link_param,
          'source_branch' => $source_branch_param,
          'branch_name' => $branch_name,
        ]);

        if ($format_param === 'json') {
          $data['created'] = TRUE;
          $data['fork'] = $fork;
          return new JsonResponse($data);
        }

        // Let the users know what happened.
        $fork_created = $fork['name'];
        $this->messenger()->addStatus($this->t('The fork "@fork" was created successfully.', [
          '@fork' => $fork_created,
        ]));
        if (empty($fork['user_added'])) {
          $this->messenger()->addWarning($this->t('Could not add your user as member of the fork, try using the "Request access" button.'));
        }
      }
      else {
        $this->messenger()->addWarning($this->t('Creating the fork failed.'));
      }
    }
    catch (\Throwable $e) {
      $this->messenger()->addError($this->t('Fork could not be created.'));
      $this->getLogger('drupalorg')->error('Error creating fork. Message: @message', [
        '@message' => $e->getMessage(),
      ]);
      $data['message'] = $this->t('Fork could not be created');
    }

    if ($format_param === 'json') {
      $this->messenger()->deleteAll();
      return new JsonResponse($data);
    }

    return $this->redirect('drupalorg.issue_fork_management', [], [
      'query' => [
        'source_link' => $source_link_param,
        'fork_created' => $fork_created ?? '',
      ],
    ]);
  }

  /**
   * Page to manage all issue forks, MRs, access, etc.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The current request.
   *
   * @return array|\Symfony\Component\HttpFoundation\JsonResponse|\Symfony\Component\HttpFoundation\Response
   *   Render array or response.
   */
  public function issueForksManagement(Request $request): array | JsonResponse | Response {
    $format_param = $request->query->get('format');
    $source_link_param = $request->query->get('source_link');
    [
      'project' => $project_id,
      'issue_iid' => $issue_iid,
      'error' => $error,
    ] = $this->getProjectAndIssueIdFromUrl($source_link_param);

    if (!empty($error)) {
      if ($format_param === 'json') {
        return new JsonResponse([
          'status' => FALSE,
          'message' => 'Something went wrong.',
        ]);
      }
      $this->messenger()->addError($error);
      return [
        '#title' => $this->t('Forks management'),
        '#markup' => $this->t('Something went wrong.'),
      ];
    }

    $client = $this->getGitLabClient();
    $issue = NULL;
    $forks = NULL;
    $branches = NULL;
    $issue_specific_branches = NULL;
    $merge_requests = NULL;
    $project = NULL;
    $status = FALSE;
    try {
      // Load the empty template and then load these via a separate request.
      if ($format_param === 'json' || $format_param === 'html' || $format_param === 'all') {
        $project = $client->projects()->show($project_id);
        $issue = $client->issues()->show($project_id, $issue_iid);
        $branches = $client->repositories()->branches($project_id);
        $merge_requests = $client->issues()->relatedMergeRequests($project_id, $issue_iid);
        $fork_name_pattern = $project['path'] . '-' . $issue_iid;
        $forks = $client->projects()->forks($project_id, [
          'search' => $fork_name_pattern,
        ]);
        if (!empty($forks)) {
          foreach ($forks as $index => $fork) {
            // Returned forks need to be 100% match. eg: abc-1 vs abc-11.
            if ($fork['name'] !== $fork_name_pattern) {
              unset($forks[$index]);
            }
            else {
              // Get issue specific branches.
              $issue_specific_branches[$fork['id']] = $client->repositories()->branches($fork['id'], [
                'search' => '^' . $issue_iid . '-',
              ]);
            }
          }
        }
        $status = TRUE;
      }
    }
    catch (\Throwable $e) {
      // Let the front-end display that there is no data.
      $this->getLogger('drupalorg')->error('Error getting GitLab info. Message: @message', [
        '@message' => $e->getMessage(),
      ]);
    }

    if (empty($issue) && $format_param === 'all') {
      $this->messenger()->addWarning($this->t('There was a problem finding the issue. Please make sure that the link provided is valid.'));
    }

    // At this point we have all the data. See the requested format and return.
    if ($format_param === 'json') {
      return new JsonResponse([
        'status' => $status,
        'forks' => $forks,
        'branches' => $branches,
        'issue_specific_branches' => $issue_specific_branches,
        'merge_requests' => $merge_requests,
        'issue' => $issue,
        'source_link' => $source_link_param,
        'project' => $project,
        'logged_in' => $this->currentUser()->isAuthenticated(),
      ]);
    }

    // Full page return.
    $build = [
      '#title' => $this->t('Forks management') . ($issue ? ': ' . $issue['title'] : ''),
      '#theme' => 'drupalorg_issue_forks_management',
      '#partial' => ($format_param === 'html'),
      '#forks' => $forks,
      '#branches' => $branches,
      '#issue_specific_branches' => $issue_specific_branches,
      '#merge_requests' => $merge_requests,
      '#issue' => $issue,
      '#source_link' => $source_link_param,
      '#project' => $project,
      '#logged_in' => $this->currentUser()->isAuthenticated(),
      '#attached' => [
        'library' => [
          'drupalorg/forks_management',
          'bluecheese/forks_management',
          // SDCs used in the markup and loaded via AJAX.
          'core/components.bluecheese--copy_code_block',
        ],
      ],
      '#cache' => [
        'max-age' => 0,
        'contexts' => [
          'url',
          'user',
        ],
      ],
    ];

    // Partial page return.
    if ($format_param === 'html') {
      // Title, libraries and caching information is for full page mode.
      unset($build['#title']);
      unset($build['#attached']);
      unset($build['#cache']);

      // Render without the layout elements, just the block.
      $output = $this->renderer->renderRoot($build);
      $response = new Response();
      $response->setContent($output);

      return $response;
    }

    return $build;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc