Cloud-CV/EvalAI

View on GitHub
frontend_v2/src/app/components/publiclists/teamlist/teamlist.component.ts

Summary

Maintainability
F
4 days
Test Coverage
import { Component, OnInit, ViewChildren, QueryList, OnDestroy } from '@angular/core';
import { ApiService } from '../../../services/api.service';
import { GlobalService } from '../../../services/global.service';
import { AuthService } from '../../../services/auth.service';
import { Router, ActivatedRoute } from '@angular/router';
import { BehaviorSubject } from 'rxjs';
import { ChallengeService } from '../../../services/challenge.service';
import { EndpointsService } from '../../../services/endpoints.service';

/**
 * Component Class
 */
@Component({
  selector: 'app-teamlist',
  templateUrl: './teamlist.component.html',
  styleUrls: ['./teamlist.component.scss'],
})
export class TeamlistComponent implements OnInit, OnDestroy {
  isnameFocused = false;
  isurlFocused = false;
  isTeamNameRequired = false;

  /**
   * Auth Service public instance
   */
  authServicePublic: any;

  /**
   * Router public instance
   */
  routerPublic: any;

  /**
   * Authentication Service subscription
   */
  authServiceSubscription: any;

  /**
   * Team list
   */
  allTeams = [];

  /**
   * Filtered team list
   */
  filteredTeams = [];

  /**
   * Filtered team observable-source
   */
  private filteredTeamSource = new BehaviorSubject(this.filteredTeams);

  /**
   * Filtered team observable
   */
  filteredTeamsObservable = this.filteredTeamSource.asObservable();

  /**
   * See more content in frame
   */
  seeMore = 1;

  /**
   * Frame size
   */
  windowSize = 2;

  /**
   * Select team title
   */
  teamSelectTitle = '';

  /**
   * Create team title
   */
  teamCreateTitle = '';

  /**
   * Create team button
   */
  teamCreateButton = '';

  /**
   * Create team Object
   */
  create_team = {
    team_name: '',
    team_url: '',
  };

  /**
   * Is a host
   */
  isHost = false;

  /**
   * Is router currently on challenges page
   */
  isOnChallengePage = false;

  /**
   * Fetch teams URL
   */
  fetchTeamsPath: any;

  /**
   * Create teams URL
   */
  createTeamsPath: any;

  /**
   * Delete teams URL
   */
  deleteTeamsPath: any;

  /**
   * Delete member URL
   */
  deleteMembersPath: any;

  /**
   * Currently selected team
   */
  selectedTeam: any = null;

  /**
   * Currently clicked team
   */
  currentlyClickedTeam: any = null;

  /**
   * challenge object
   */
  challenge: any;

  /**
   * To call the API inside the modal
   */
  apiCall: any;

  /**
   * Form components
   */
  teamForm = 'formteam';

  /**
   * Form fields in the team form (team cards)
   */
  @ViewChildren('formteam')
  components: QueryList<TeamlistComponent>;

  /**
   * Routh path for login
   */
  authRoutePath = '/auth/login';

  /**
   * Route path for host teams
   */
  hostTeamRoutePath = '/teams/hosts';

  /**
   * Route path for participant teams
   */
  participantTeamRoutePath = '/teams/participants';

  /**
   * Route path for create challenge
   */
  createChallengeRoutePath = '/challenge-create';

  /**
   * Route path for create template challenge page
   */
  createTemplateChallengeRoutePath = '/template-challenge-create';

  /**
   * Filter query as participant team name
   */
  filterTeamsQuery = '';

  /**
   * Content for terms and conditions
   */
  termsAndConditionContent = [
    'Participants can train their models with the released training set' +
      ', also with other training images as long as they are disjoint with the test set' +
      '(see <a class="blue-text" href="https://arxiv.org/pdf/1804.09691.pdf" target="_blank">arXiv paper</a>)',

    'We provide the evaluation codes, along with the released validation dataset, ' +
      'for participants to validate their algorithms on validation set during the <strong>validation phase</strong>.' +
      'The usage instruction is described in "readme.txt" released along with the train/validation data. ',

    'For <strong>testing phase</strong> of <strong>identification</strong> track, ' +
      'we will release the test set where gallery set is fully labelled with idenitities, ' +
      'and probe images with only pseudo labels (not related to identities). ' +
      "Participants will submit a file reporting each probe image's " +
      'feature distance to the top-20 matching gallery identities and the corresponding identity indexes ' +
      '(see "submission format" on the "Overview" page), and then their performance will be measured by our system.   ',

    'For <strong>testing phase</strong> of <strong>verification</strong> track, ' +
      'we will release the images with only pseudo labels and the constructed image pairs, ' +
      'without indicating they are positive or negative. ' +
      "Participants will submit a file reporting each image pair's matching socre, " +
      'and then their performance will be measured by our system.   ',
  ];

  /**
   * Team list route's query parameter subscription
   */
  teamListRouteQueryParamSub = null;

  /**
   * Is the team being selected for a template based challenge or not
   */
  isTemplateChallenge = false;

  /**
   * Id of the challenge template selected
   */
  templateId = null;

  /**
   * Number of phases in the challenge template
   */
  templatePhases = null;

  /**
   * Constructor.
   * @param route  ActivatedRoute Injection.
   * @param router  Router Injection.
   * @param globalService  GlobalService Injection.
   * @param authService  AuthService Injection.
   * @param apiService  ApiService Injection.
   * @param challengeService  ChallengeService Injection.
   * @param endpointsService  EndpointsService Injection.
   */
  constructor(
    private apiService: ApiService,
    private authService: AuthService,
    private globalService: GlobalService,
    private router: Router,
    private route: ActivatedRoute,
    private challengeService: ChallengeService,
    private endpointsService: EndpointsService
  ) {}

  /**
   * Component on initialized.
   */
  ngOnInit() {
    this.routerPublic = this.router;

    if (!this.authService.isLoggedIn()) {
      this.router.navigate([this.authRoutePath]);
    }

    this.authServicePublic = this.authService;

    this.authServiceSubscription = this.authService.change.subscribe((authState) => {
      if (!authState.isLoggedIn) {
        this.router.navigate([this.authRoutePath]);
      }
    });

    this.teamListRouteQueryParamSub = this.route.queryParams.subscribe((params) => {
      this.isTemplateChallenge = params['template'] || false;
      if (this.isTemplateChallenge) {
        this.templateId = params['templateId'];
        this.templatePhases = params['templatePhases'];
      }
    });

    if (this.router.url === this.hostTeamRoutePath || this.isTemplateChallenge) {
      this.isHost = true;
      this.fetchTeamsPath = 'hosts/challenge_host_team';
      this.createTeamsPath = 'hosts/create_challenge_host_team';
      this.deleteTeamsPath = 'hosts/remove_self_from_challenge_host';
      this.deleteMembersPath = 'hosts/challenge_host_team/<team_id>/challenge_host/';
      this.fetchMyTeams(this.fetchTeamsPath);
      this.teamCreateTitle = 'Create a New Team';
      this.teamSelectTitle = 'Please Select a Challenge Host Team';
      this.teamCreateButton = 'Create Host Team';
    } else {
      if (this.router.url !== this.participantTeamRoutePath) {
        this.isOnChallengePage = true;
        this.challengeService.currentChallenge.subscribe((challenge) => (this.challenge = challenge));
      }
      this.fetchTeamsPath = 'participants/participant_team';
      this.createTeamsPath = this.fetchTeamsPath;
      this.deleteTeamsPath = 'participants/remove_self_from_participant_team';
      this.deleteMembersPath = 'participants/participant_team/<team_id>/participant/';
      this.fetchMyTeams(this.fetchTeamsPath);
      this.teamCreateTitle = 'Create a New Participant Team';
      this.teamSelectTitle = 'My Existing Participant Teams';
      this.teamCreateButton = 'Create Participant Team';
    }
  }

  ngOnDestroy() {
    if (this.authServiceSubscription) {
      this.authServiceSubscription.unsubscribe();
    }
    this.teamListRouteQueryParamSub.unsubscribe();
  }

  /**
   * Show more results.
   */
  seeMoreClicked() {
    this.seeMore = this.seeMore + 1;
    this.updateTeamsView(false);
  }

  /**
   * Show less results.
   */
  seeLessClicked() {
    this.seeMore = this.seeMore - 1;
    this.updateTeamsView(false);
  }

  /**
   * Update teams view (called after fetching teams from API).
   * @param reset  reset flag
   */
  updateTeamsView(reset) {
    if (reset) {
      this.seeMore = 1;
    }
    this.filteredTeams = this.allTeams.slice((this.seeMore - 1) * this.windowSize, this.seeMore * this.windowSize);
    this.filteredTeamSource.next(this.filteredTeams);
  }

  /**
   * Fetch my teams from a given URL.
   * @param path  Fetch teams URL
   */
  fetchMyTeams(path) {
    if (this.authService.isLoggedIn()) {
      if (this.filterTeamsQuery === '') {
        this.fetchTeams(path);
      } else {
        this.filterTeam(this.filterTeamsQuery);
      }
    }
  }

  /**
   * Select a team and unselect others.
   */
  selectTeamWrapper() {
    const SELF = this;
    const selectTeam = (team) => {
      SELF.selectedTeam = team;
    };
    return selectTeam;
  }

  /**
   * Unselect a team.
   */
  deselectTeamWrapper() {
    const SELF = this;
    const selectTeam = (team) => {
      SELF.currentlyClickedTeam = team;
      SELF.unselectOtherTeams(SELF);
    };
    return selectTeam;
  }

  /**
   * Unselecting other teams function.
   */
  unselectOtherTeams(self) {
    const temp = self.allTeams.slice();
    for (let i = 0; i < temp.length; i++) {
      temp[i]['isSelected'] = false;
      if (self.currentlyClickedTeam['id'] === temp[i]['id']) {
        temp[i]['isSelected'] = true;
      }
    }
    self.allTeams = temp;
    self.updateTeamsView(false);
  }

  /**
   * Append additional parameters to teams objects.
   */
  appendIsSelected(teams) {
    for (let i = 0; i < teams.length; i++) {
      teams[i]['isSelected'] = false;
      teams[i]['isHost'] = this.isHost;
    }
    return teams;
  }

  /**
   * Fetch teams from backend.
   * @param path  Fetch teams URL.
   */
  fetchTeams(path) {
    const SELF = this;
    this.globalService.startLoader('Fetching Teams');
    this.apiService.getUrl(path).subscribe(
      (data) => {
        this.globalService.stopLoader();
        SELF.updateTeamsData(data);
      },
      (err) => {
        if (err.status === 403) {
          this.router.navigate(['permission-denied']);
        }
        this.globalService.stopLoader();
        SELF.globalService.handleApiError(err, false);
      },
      () => {}
    );
  }

  /**
   * Display confirm dialog before deleting a team.
   */
  deleteTeamWrapper() {
    const SELF = this;
    const deleteTeam = (e) => {
      SELF.apiCall = () => {
        SELF.apiService.deleteUrl(SELF.deleteTeamsPath + '/' + e).subscribe(
          (data) => {
            // Success Message in data.message
            SELF.globalService.showToast('success', 'You were removed from the team!', 5);
            SELF.fetchMyTeams(SELF.fetchTeamsPath);
            SELF.selectedTeam = null;
          },
          (err) => {
            SELF.globalService.handleApiError(err);
          },
          () => {}
        );
      };
      const PARAMS = {
        title: 'Would you like to remove yourself ?',
        content: 'Note: This action will remove you from the team',
        confirm: 'Yes',
        deny: 'Cancel',
        confirmCallback: SELF.apiCall,
      };
      SELF.globalService.showConfirm(PARAMS);
      return false;
    };
    return deleteTeam;
  }

  /**
   * Display Modal for editing team details.
   */
  editTeamWrapper() {
    const SELF = this;
    let TeamUrl;
    const editTeam = (team) => {
      const teamId = team['id'];
      const teamName = team['team_name'];
      const teamUrl = team['team_url'];
      TeamUrl = this.isHost
        ? SELF.endpointsService.hostTeamURL(teamId)
        : SELF.endpointsService.participantTeamURL(teamId);
      SELF.apiCall = (params) => {
        const BODY = JSON.stringify(params);
        SELF.apiService.patchUrl(TeamUrl, BODY).subscribe(
          (data) => {
            // Success Message in data.message
            SELF.globalService.showToast('success', 'Team Updated', 5);
            SELF.fetchMyTeams(SELF.fetchTeamsPath);
            SELF.selectedTeam = null;
          },
          (err) => {
            SELF.globalService.handleApiError(err);
          },
          () => {}
        );
      };
      const PARAMS = {
        title: 'Edit team details',
        content: '',
        confirm: 'Confirm',
        deny: 'Cancel',
        form: [
          {
            isRequired: false,
            label: 'team_name',
            placeholder: 'Team Name',
            value: teamName,
            type: 'text',
          },
          {
            isRequired: false,
            label: 'team_url',
            placeholder: 'Team URL',
            value: teamUrl,
            type: 'text',
          },
        ],
        isButtonDisabled: true,
        confirmCallback: SELF.apiCall,
      };
      SELF.globalService.showModal(PARAMS);
    };
    return editTeam;
  }

  /**
   * Display modal to add members to the team.
   */
  addMembersToTeamWrapper() {
    const SELF = this;
    const addMembersToTeam = (team) => {
      SELF.apiCall = (params) => {
        const BODY = JSON.stringify(params);
        let apiPath = SELF.endpointsService.participantTeamInviteURL(team);
        if (SELF.isHost) {
          apiPath = SELF.endpointsService.hostTeamInviteURL(team);
        }
        SELF.apiService.postUrl(apiPath, BODY).subscribe(
          (data) => {
            // Success Message in data.message
            SELF.globalService.showToast('success', 'User added to the team successfully', 5);
            SELF.fetchMyTeams(SELF.fetchTeamsPath);
            SELF.selectedTeam = null;
          },
          (err) => {
            SELF.globalService.handleApiError(err, true);
          },
          () => {}
        );
      };
      const PARAMS = {
        title: 'Invite members to the team',
        content: '',
        confirm: 'ADD',
        deny: 'Cancel',
        form: [
          {
            isRequired: true,
            label: 'email',
            placeholder: 'Enter the email address',
            type: 'email',
          },
        ],
        isButtonDisabled: true,
        confirmCallback: SELF.apiCall,
      };
      SELF.globalService.showModal(PARAMS);
    };
    return addMembersToTeam;
  }

  /**
   * Create Team function.
   * @param formname  name of form fields (#).
   */
  createTeam(formname) {
    this.globalService.formValidate(this.components, this.createTeamSubmit, this);
  }

  /**
   * Called after create team form is validated.
   * @param isvalidForm
   */
  createTeamSubmit(isvalidForm) {
    if (isvalidForm) {
      const API_PATH = this.createTeamsPath;
      const url = this.create_team['team_url'];
      let TEAM_BODY: any = {
        team_name: this.create_team['team_name'],
      };
      if (url) {
        TEAM_BODY['team_url'] = url;
      }
      TEAM_BODY = JSON.stringify(TEAM_BODY);
      this.globalService.startLoader('Creating Team');
      this.apiService.postUrl(API_PATH, TEAM_BODY).subscribe(
        (data) => {
          this.globalService.stopLoader();
          // Success Message in data.message
          this.globalService.showToast('success', 'Team created successfully!', 5);
          this.fetchMyTeams(this.fetchTeamsPath);

          // Reset input
          this.create_team = {
            team_url: '',
            team_name: '',
          };

          this.isnameFocused = false;
          this.isurlFocused = false;
          this.isTeamNameRequired = false;
        },
        (err) => {
          this.globalService.stopLoader();
          this.globalService.showToast('error', err.error.team_name, 5);
          this.globalService.handleFormError(this.components, err);
        },
        () => {}
      );
    } else if (this.create_team['team_name'] === '') {
      this.isTeamNameRequired = true;
    }
  }

  /**
   * Create challenge (redirect).
   */
  createChallenge() {
    this.challengeService.changeCurrentHostTeam(this.selectedTeam);
    this.router.navigate([this.createChallengeRoutePath]);
  }

  createChallengeFromTemplate() {
    this.challengeService.changeCurrentHostTeam(this.selectedTeam);
    this.router.navigate([this.createTemplateChallengeRoutePath, this.templateId, this.templatePhases]);
  }

  /**
   * Display confirm dialog before deleting a team member.
   */
  deleteTeamMemberWrapper() {
    const SELF = this;
    const deleteTeamMember = (team) => {
      const deleteUrl = SELF.deleteMembersPath.replace('<team_id>', team.teamId);
      SELF.apiCall = (params) => {
        SELF.apiService.deleteUrl(deleteUrl + team.participantId).subscribe(
          (data) => {
            // Success Message in data.message
            SELF.globalService.showToast('success', 'Member was removed from the team!', 5);
            SELF.fetchMyTeams(SELF.fetchTeamsPath);
            SELF.selectedTeam = null;
          },
          (err) => {
            SELF.globalService.handleApiError(err);
          },
          () => {}
        );
      };
      const PARAMS = {
        title: 'Would you like to remove this member ?',
        content: 'Note: This action will remove this member from the team',
        confirm: 'Yes',
        deny: 'Cancel',
        confirmCallback: SELF.apiCall,
      };
      SELF.globalService.showConfirm(PARAMS);
      return false;
    };
    return deleteTeamMember;
  }

  /**
   * Filter teams by team name
   * @param teamName Participant team name
   */
  filterTeam(teamName) {
    const SELF = this;
    SELF.filterTeamsQuery = teamName;
    let API_PATH;
    if (SELF.isHost) {
      API_PATH = SELF.endpointsService.FilteredHostTeamURL(teamName);
    } else {
      API_PATH = SELF.endpointsService.FilteredParticipantTeamURL(teamName);
    }
    const name = SELF.filterTeamsQuery;
    SELF.apiService.getUrl(API_PATH).subscribe(
      (data) => {
        if (name === SELF.filterTeamsQuery) {
          SELF.updateTeamsData(data);
        }
      },
      (err) => {
        SELF.globalService.handleApiError(err, true);
      },
      () => {}
    );
  }

  /**
   * Update teams data.
   * @param teamsData  Fetched teams data.
   */
  updateTeamsData(teamsData) {
    if (teamsData['results']) {
      this.allTeams = teamsData['results'];
      if (this.isHost || this.isOnChallengePage) {
        this.allTeams = this.appendIsSelected(this.allTeams);
      }
      this.updateTeamsView(true);
    }
  }
}