diff --git a/api/schema/resolvers/IssueResolver.js b/api/schema/resolvers/IssueResolver.js index 6b21ef1b..18f85758 100644 --- a/api/schema/resolvers/IssueResolver.js +++ b/api/schema/resolvers/IssueResolver.js @@ -1,3 +1,6 @@ +const moment = require('moment') +const { Op } = require('sequelize') + module.exports = { Issue: { @@ -13,8 +16,22 @@ module.exports = { return models.Issue.findByPk(id) }, - getIssuesByProjectId: async (root, { project_id }, { models }) => { - return models.Issue.findAll({ where: { project_id } }) + getIssuesByProjectId: async (root, { project_id, limit, offset, last_30_days_only }, { models }) => { + const issuesConditions = { + project_id + } + if (last_30_days_only) { + issuesConditions.created_at = { + [Op.gte]: `${moment().subtract(30, 'days').toDate()}` + } + } + return models.Issue.findAll({ + where: issuesConditions, + limit: limit, + offset: offset, + order: [['date_opened', 'DESC']] + + }) } }, diff --git a/api/schema/resolvers/ProjectResolver.js b/api/schema/resolvers/ProjectResolver.js index 1de385d2..844ee323 100644 --- a/api/schema/resolvers/ProjectResolver.js +++ b/api/schema/resolvers/ProjectResolver.js @@ -165,7 +165,12 @@ module.exports = { }) }, issues: (project, args, { models }) => { - return models.Issue.findAll({ where: { project_id: project.id } }) + return models.Issue.findAll({ + where: { project_id: project.id }, + limit: args.limit, + offset: args.offset, + order: [['date_opened', 'DESC']] + }) }, permissions: (project, args, { models }) => { return models.Permission.findAll({ diff --git a/api/schema/types/IssueType.js b/api/schema/types/IssueType.js index 22449f16..7288496c 100644 --- a/api/schema/types/IssueType.js +++ b/api/schema/types/IssueType.js @@ -23,7 +23,12 @@ module.exports = gql` type Query { getIssueById(id: Int!): Issue - getIssuesByProjectId(project_id: Int!): [Issue] + getIssuesByProjectId( + project_id: Int! + limit: Int + offset: Int + last_30_days_only: Int + ): [Issue] } type Mutation { diff --git a/api/schema/types/ProjectType.js b/api/schema/types/ProjectType.js index 73782a4e..6cd4f2ff 100644 --- a/api/schema/types/ProjectType.js +++ b/api/schema/types/ProjectType.js @@ -23,7 +23,10 @@ module.exports = gql` contributors: [Contributor] githubContributors: [Contributor] issuesOpened(fromDate: String, toDate: String): Int - issues: [Issue] + issues( + limit: Int, + offset: Int + ): [Issue] permissions: [Permission] githubIssuesOpened( fromDate: String, diff --git a/src/components/GithubAccessBlocked.js b/src/components/GithubAccessBlocked.js index c168ab2c..ecdcdc1a 100644 --- a/src/components/GithubAccessBlocked.js +++ b/src/components/GithubAccessBlocked.js @@ -5,7 +5,7 @@ import { Typography, Link } from '@material-ui/core' -import { useLazyQuery } from '@apollo/client'; +import { useLazyQuery, useQuery } from '@apollo/client'; import { GET_PROJECT } from '../operations/queries/ProjectQueries'; const GithubAccessBlocked = (props) => { @@ -13,7 +13,7 @@ const GithubAccessBlocked = (props) => { const githubURL = props.githubURL const [projectGithubUrl, setProjectGithubUrl] = useState(null) - + const [getProject, { data: dataProject, loading: loadingProject, diff --git a/src/components/IssueTile.js b/src/components/IssueTile.js index 04116f5f..a6440a03 100644 --- a/src/components/IssueTile.js +++ b/src/components/IssueTile.js @@ -12,7 +12,7 @@ const IssueTile = (props) => { const { issue } = props const issueIsOpen = issue.date_closed ? false : true - const renderContributions = (contributions) => { + const renderContributions = (contributions = []) => { return contributions.map(i => { return ( diff --git a/src/components/ProjectIssues.js b/src/components/ProjectIssues.js index c057ce77..dec2be39 100644 --- a/src/components/ProjectIssues.js +++ b/src/components/ProjectIssues.js @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useState } from 'react' import { useMutation, useQuery } from '@apollo/client' import { Box, @@ -13,32 +13,49 @@ import IssueTile from './IssueTile' import LoadingProgress from './LoadingProgress' import ProjectIssuesMetrics from './ProjectIssuesMetrics' -import { GET_PROJECT_ISSUES } from '../operations/queries/ProjectQueries' +import { GET_PROJECT_METRICS } from '../operations/queries/ProjectQueries' +import { GET_ISSUES_BY_PROJECT } from '../operations/queries/IssueQueries' import { SYNC_GITHUB_ISSUES } from '../operations/mutations/ProjectMutations' import { pageName } from '../reactivities/variables' +import { ISSUES_LIMIT } from '../constants' const ProjectIssues = (props) => { const { projectId } = props - const last30DayIssues = [] const today30DaysAgo = moment().subtract(30, 'days').format('x') + const [moreIssuesToLoad, setMoreIssuesToLoad] = useState(true) + const [last30DayIssuesCount, setLast30DayIssuesCount] = useState(0) + const { data: dataProjectIssues, loading: loadingProjectIssues, - error: errorProjectIssues - } = useQuery(GET_PROJECT_ISSUES, { + error: errorProjectIssues, + } = useQuery(GET_PROJECT_METRICS, { variables: { id: Number(projectId) } }) + const { + data: dataIssues, + loading: loadingIssues, + error: errorIssues, + fetchMore: fetchMoreIssues + } = useQuery(GET_ISSUES_BY_PROJECT, { + variables: { + projectId: Number(projectId), + offset: 0, + last30DaysOnly: 1 + } + }) + const [ getIssues, { - data: dataIssues, - loading: loadingIssues, - error: errorIssues + data: dataGitHubIssues, + loading: loadingGitHubIssues, + error: errorGitHubIssues } ] = useMutation(SYNC_GITHUB_ISSUES, { variables: { @@ -46,14 +63,27 @@ const ProjectIssues = (props) => { }, refetchQueries: [ { - query: GET_PROJECT_ISSUES, + query: GET_ISSUES_BY_PROJECT, variables: { - id: Number(projectId) + projectId: Number(projectId), + offset: 0, + last30DaysOnly: 1 } } ] }) + const loadMoreIssues = async ({ issues }) => { + const moreIssues = await fetchMoreIssues({ + variables: { + offset: issues.length + } + }) + if (moreIssues.data.getIssuesByProjectId.length < ISSUES_LIMIT) { + setMoreIssuesToLoad(false) + } + } + const renderIssues = (issues) => { return issues.map(i => { return ( @@ -64,8 +94,8 @@ const ProjectIssues = (props) => { }) } - if (loadingProjectIssues) return - if (errorProjectIssues) { + if (loadingProjectIssues || loadingIssues) return + if (errorProjectIssues || errorIssues) { return ( { } const { getProjectById: project } = dataProjectIssues + const { getIssuesByProjectId: issues } = dataIssues + pageName(project.name) - project.issues.map(i => { - if (i['date_opened'] >= today30DaysAgo) { - last30DayIssues.push(i) - } - }) - const sortedIssues = orderBy(last30DayIssues, ['date_closed'], ['desc']) + + const sortedIssues = orderBy(issues, ['date_closed'], ['desc']) return ( @@ -108,12 +136,21 @@ const ProjectIssues = (props) => { - {loadingIssues && + {loadingGitHubIssues && } {renderIssues(sortedIssues)} + {moreIssuesToLoad && + + } diff --git a/src/constants/index.js b/src/constants/index.js index 7fa6c5db..c584b46e 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -66,6 +66,7 @@ export const EXPECTED_BUDGET_TIMEFRAME_OPTIONS = [ } ] export const IS_PRODUCTION = process.env.NODE_ENV == 'production' ? true : false +export const ISSUES_LIMIT = 20 export const LOGO_URL = 'https://project-trinary.s3.amazonaws.com/images/Logo.png' export const MAX_INT = 2147483647 export const NAV_ITEMS = [ diff --git a/src/index.js b/src/index.js index 52e40091..e3a134e7 100644 --- a/src/index.js +++ b/src/index.js @@ -2,12 +2,21 @@ import React from 'react' import ReactDOM from 'react-dom' import { BrowserRouter } from 'react-router-dom' import { ApolloClient, ApolloProvider, InMemoryCache, createHttpLink } from '@apollo/client' +import { offsetLimitPagination } from '@apollo/client/utilities'; import './styles/index.scss' import App from './App' import { API_ROOT } from './constants' -const cache = new InMemoryCache() +const cache = new InMemoryCache({ + typePolicies: { + Query: { + fields: { + getIssuesByProjectId: offsetLimitPagination() + } + } + } +}) const uri = `${API_ROOT}/graph` const link = createHttpLink({ uri, diff --git a/src/operations/queries/IssueQueries.js b/src/operations/queries/IssueQueries.js index 935e5837..73309582 100644 --- a/src/operations/queries/IssueQueries.js +++ b/src/operations/queries/IssueQueries.js @@ -1 +1,25 @@ import { gql } from '@apollo/client'; + +import { ISSUES_LIMIT } from '../../constants' + +export const GET_ISSUES_BY_PROJECT = gql` + query IssuesFromProject ( + $projectId: Int!, + $offset: Int, + $last30DaysOnly: Int + ) { + getIssuesByProjectId( + project_id: $projectId, + offset: $offset, + limit: ${ISSUES_LIMIT} + last_30_days_only: $last30DaysOnly + ) { + id + github_url + github_number + name + date_opened + date_closed + } + } +` diff --git a/src/operations/queries/ProjectQueries.js b/src/operations/queries/ProjectQueries.js index 11b5f1b6..7ef4a6cc 100644 --- a/src/operations/queries/ProjectQueries.js +++ b/src/operations/queries/ProjectQueries.js @@ -132,8 +132,12 @@ export const GET_PROJECT_CONTRIBUTORS = gql` } } ` -export const GET_PROJECT_ISSUES = gql` - query ProjectTimeEntries($id: Int!, $issuesFromDate: String, $issuesToDate: String){ +export const GET_PROJECT_METRICS = gql` + query ProjectTimeEntries( + $id: Int!, + $issuesFromDate: String, + $issuesToDate: String + ){ getProjectById(id: $id){ id name @@ -284,4 +288,4 @@ export const GET_INACTIVE_PROJECTS = gql` } } } -` \ No newline at end of file +`