From 023f8b03e2574b9067b8cf117f28b52f7b8ccaaa Mon Sep 17 00:00:00 2001 From: Jim O'Donnell Date: Fri, 19 Feb 2021 13:25:32 +0000 Subject: [PATCH] Split code path for grouped workflows Only use the Cellect API for grouped workflows. Return a default set of workflow data otherwise. Update tests to test for grouped and non-grouped workflows. Update mocks to represent Cellect responses without the grouped attribute. --- .../fetchWorkflowsHelper.js | 40 +-- .../fetchWorkflowsHelper.spec.js | 28 +- .../getDefaultPageProps.spec.js | 283 +++++++++++------- .../getStaticPageProps.spec.js | 279 ++++++++++------- 4 files changed, 387 insertions(+), 243 deletions(-) diff --git a/packages/app-project/src/helpers/fetchWorkflowsHelper/fetchWorkflowsHelper.js b/packages/app-project/src/helpers/fetchWorkflowsHelper/fetchWorkflowsHelper.js index d4a8581a3a..eec598b11e 100644 --- a/packages/app-project/src/helpers/fetchWorkflowsHelper/fetchWorkflowsHelper.js +++ b/packages/app-project/src/helpers/fetchWorkflowsHelper/fetchWorkflowsHelper.js @@ -2,14 +2,14 @@ import { panoptes } from '@zooniverse/panoptes-js' import fetch from 'node-fetch' async function fetchWorkflowData (activeWorkflows, env) { - const response = await panoptes - .get('/workflows', { - complete: false, - env, - fields: 'completeness,grouped', - id: activeWorkflows.join(','), - include: 'subject_sets' - }) + const query = { + complete: false, + env, + fields: 'completeness,grouped', + id: activeWorkflows.join(','), + include: 'subject_sets' + } + const response = await panoptes.get('/workflows', query) const { workflows, linked } = response.body const subjectSets = linked ? linked.subject_sets : [] await Promise.allSettled(subjectSets.map(subjectSet => fetchPreviewImage(subjectSet, env))) @@ -33,7 +33,7 @@ async function fetchWorkflowCellectStatus(workflow) { const workflowURL = `https://cellect.zooniverse.org/workflows/${workflow.id}/status` const response = await fetch(workflowURL) const body = await response.json() - const { groups } = body ?? {} + const groups = body.groups ?? {} return groups } @@ -50,6 +50,17 @@ async function fetchPreviewImage (subjectSet, env) { } async function buildWorkflow(workflow, displayName, subjectSets, isDefault) { + const workflowData = { + completeness: workflow.completeness || 0, + default: isDefault, + displayName, + grouped: false, + id: workflow.id, + subjectSets: [] + } + if (!workflow.grouped) { + return workflowData + } const subjectSetCounts = await fetchWorkflowCellectStatus(workflow) const workflowSubjectSets = workflow.links.subject_sets .map(subjectSetID => { @@ -59,14 +70,7 @@ async function buildWorkflow(workflow, displayName, subjectSets, isDefault) { }) .filter(Boolean) - return { - completeness: workflow.completeness || 0, - default: isDefault, - displayName, - grouped: workflow.grouped, - id: workflow.id, - subjectSets: workflowSubjectSets - } + return Object.assign(workflowData, { grouped: true, subjectSets: workflowSubjectSets }) } async function fetchWorkflowsHelper (language = 'en', activeWorkflows, defaultWorkflow, env) { @@ -80,7 +84,7 @@ async function fetchWorkflowsHelper (language = 'en', activeWorkflows, defaultWo return buildWorkflow(workflow, displayName, subjectSets, isDefault) }) const workflowStatuses = await Promise.allSettled(awaitWorkflows) - const workflowsWithSubjectSets = workflowStatuses.map(result => result.value) + const workflowsWithSubjectSets = workflowStatuses.map(result => result.value || result.reason) return workflowsWithSubjectSets } diff --git a/packages/app-project/src/helpers/fetchWorkflowsHelper/fetchWorkflowsHelper.spec.js b/packages/app-project/src/helpers/fetchWorkflowsHelper/fetchWorkflowsHelper.spec.js index fa7f5dfb27..9e01e932a8 100644 --- a/packages/app-project/src/helpers/fetchWorkflowsHelper/fetchWorkflowsHelper.spec.js +++ b/packages/app-project/src/helpers/fetchWorkflowsHelper/fetchWorkflowsHelper.spec.js @@ -15,7 +15,7 @@ describe('Helpers > fetchWorkflowsHelper', function () { { id: '2', completeness: 0.7, - grouped: false, + grouped: true, links: { subject_sets: ['1', '2', '3'] } @@ -56,9 +56,7 @@ describe('Helpers > fetchWorkflowsHelper', function () { const cellect = nock('https://cellect.zooniverse.org') .persist() .get('/workflows/1/status') - .reply(200, { - groups: availableSubjects - }) + .reply(200, {}) .get('/workflows/2/status') .reply(200, { groups: availableSubjects @@ -98,11 +96,7 @@ describe('Helpers > fetchWorkflowsHelper', function () { grouped: false, id: '1', displayName: 'Foo', - subjectSets: [ - Object.assign(subjectSet('1'), { availableSubjects: availableSubjects[1]}), - Object.assign(subjectSet('2'), { availableSubjects: availableSubjects[2]}), - Object.assign(subjectSet('3'), { availableSubjects: availableSubjects[3]}) - ] + subjectSets: [] } ]) }) @@ -135,16 +129,12 @@ describe('Helpers > fetchWorkflowsHelper', function () { grouped: false, id: '1', displayName: 'Foo', - subjectSets: [ - Object.assign(subjectSet('1'), { availableSubjects: availableSubjects[1]}), - Object.assign(subjectSet('2'), { availableSubjects: availableSubjects[2]}), - Object.assign(subjectSet('3'), { availableSubjects: availableSubjects[3]}) - ] + subjectSets: [] }, { completeness: 0.7, default: false, - grouped: false, + grouped: true, id: '2', displayName: 'Bar', subjectSets: [ @@ -185,16 +175,12 @@ describe('Helpers > fetchWorkflowsHelper', function () { grouped: false, id: '1', displayName: 'Foo', - subjectSets: [ - Object.assign(subjectSet('1'), { availableSubjects: availableSubjects[1]}), - Object.assign(subjectSet('2'), { availableSubjects: availableSubjects[2]}), - Object.assign(subjectSet('3'), { availableSubjects: availableSubjects[3]}) - ] + subjectSets: [] }, { completeness: 0.7, default: true, - grouped: false, + grouped: true, id: '2', displayName: 'Bar', subjectSets: [ diff --git a/packages/app-project/src/helpers/getDefaultPageProps/getDefaultPageProps.spec.js b/packages/app-project/src/helpers/getDefaultPageProps/getDefaultPageProps.spec.js index f85aa7627e..d4c3fa58b2 100644 --- a/packages/app-project/src/helpers/getDefaultPageProps/getDefaultPageProps.spec.js +++ b/packages/app-project/src/helpers/getDefaultPageProps/getDefaultPageProps.spec.js @@ -13,6 +13,16 @@ describe('Components > ProjectHomePage > getDefaultPageProps', function () { } } + const GROUPED_PROJECT = { + id: '2', + default_workflow: '2', + primary_language: 'en', + slug: 'test-owner/grouped-project', + links: { + active_workflows: ['2'] + } + } + const TRANSLATION = { translated_id: 1, strings: { @@ -20,6 +30,13 @@ describe('Components > ProjectHomePage > getDefaultPageProps', function () { } } + const GROUPED_TRANSLATION = { + translated_id: 2, + strings: { + display_name: 'Bar' + } + } + const WORKFLOW = { id: '1', completeness: 0.4, @@ -29,6 +46,15 @@ describe('Components > ProjectHomePage > getDefaultPageProps', function () { } } + const GROUPED_WORKFLOW = { + id: '2', + completeness: 0.4, + grouped: true, + links: { + subject_sets: ['1', '2', '3'] + } + } + const availableSubjects = { 1: 4, 2: 10, @@ -43,53 +69,82 @@ describe('Components > ProjectHomePage > getDefaultPageProps', function () { } } - describe('with the staging API', function () { - before(function () { - const slug = 'test-owner/test-project' - const cellect = nock('https://cellect.zooniverse.org') + function mockAPI(panoptesHost) { + const cellect = nock('https://cellect.zooniverse.org') + .persist() + .get('/workflows/2/status') + .reply(200, { + groups: availableSubjects + }) + const scope = nock(panoptesHost) .persist() - .get('/workflows/1/status') + .get('/projects') + .query(query => query.slug === 'test-owner/test-project') .reply(200, { - groups: availableSubjects + projects: [PROJECT] }) - const scope = nock('https://panoptes-staging.zooniverse.org/api') - .persist() - .get('/projects') - .query(query => query.slug === slug) - .reply(200, { - projects: [PROJECT] - }) - .get('/projects') - .query(query => query.slug !== slug) - .reply(200, { - projects: [] - }) - .get('/translations') - .query(query => { - return query.translated_type === 'workflow' - && query.translated_id === '1' - && query.language === 'en' - }) - .reply(200, { - translations: [TRANSLATION] - }) - .get('/workflows') - .query(query => query.id === '1') - .reply(200, { - workflows: [WORKFLOW], - linked: { - subject_sets: [ - subjectSet('1'), - subjectSet('2'), - subjectSet('3') - ] - } - }) - .get('/workflows') - .query(query => query.id !== '1') - .reply(200, { - workflows: [] - }) + .get('/projects') + .query(query => query.slug === 'test-owner/grouped-project') + .reply(200, { + projects: [GROUPED_PROJECT] + }) + .get('/projects') + .query(query => query.slug === 'test-owner/test-wrong-project') + .reply(200, { + projects: [] + }) + .get('/translations') + .query(query => { + return query.translated_type === 'workflow' + && query.translated_id === '1' + && query.language === 'en' + }) + .reply(200, { + translations: [TRANSLATION] + }) + .get('/translations') + .query(query => { + return query.translated_type === 'workflow' + && query.translated_id === '2' + && query.language === 'en' + }) + .reply(200, { + translations: [GROUPED_TRANSLATION] + }) + .get('/workflows') + .query(query => query.id === '1') + .reply(200, { + workflows: [WORKFLOW], + linked: { + subject_sets: [ + subjectSet('1'), + subjectSet('2'), + subjectSet('3') + ] + } + }) + .get('/workflows') + .query(query => query.id === '2') + .reply(200, { + workflows: [GROUPED_WORKFLOW], + linked: { + subject_sets: [ + subjectSet('1'), + subjectSet('2'), + subjectSet('3') + ] + } + }) + .get('/workflows') + .query(query => parseInt(query.id) > 2) + .reply(200, { + workflows: [] + }) + } + + describe('with the staging API', function () { + before(function () { + mockAPI('https://panoptes-staging.zooniverse.org/api') }) after(function () { @@ -122,11 +177,7 @@ describe('Components > ProjectHomePage > getDefaultPageProps', function () { grouped: false, id: '1', displayName: 'Foo', - subjectSets: [ - Object.assign(subjectSet('1'), { availableSubjects: availableSubjects[1]}), - Object.assign(subjectSet('2'), { availableSubjects: availableSubjects[2]}), - Object.assign(subjectSet('3'), { availableSubjects: availableSubjects[3]}) - ] + subjectSets: [] } ]) }) @@ -169,6 +220,43 @@ describe('Components > ProjectHomePage > getDefaultPageProps', function () { }) }) + describe('with a grouped workflow', function () { + it('should return the project\'s active workflows with subject sets', async function () { + const params = { + owner: 'test-owner', + project: 'grouped-project', + workflowID: '2' + } + const query = { + env: 'staging' + } + const req = { + connection: { + encrypted: true + }, + headers: { + host: 'www.zooniverse.org' + } + } + const res = {} + const { props } = await getDefaultPageProps({ params, query, req, res }) + expect(props.workflows).to.deep.equal([ + { + completeness: 0.4, + default: true, + grouped: true, + id: '2', + displayName: 'Bar', + subjectSets: [ + Object.assign(subjectSet('1'), { availableSubjects: availableSubjects[1]}), + Object.assign(subjectSet('2'), { availableSubjects: availableSubjects[2]}), + Object.assign(subjectSet('3'), { availableSubjects: availableSubjects[3]}) + ] + } + ]) + }) + }) + describe('with an invalid workflow ID', function () { let props let res = {} @@ -177,7 +265,7 @@ describe('Components > ProjectHomePage > getDefaultPageProps', function () { const params = { owner: 'test-owner', project: 'test-project', - workflowID: '2' + workflowID: '3' } const query = { env: 'staging' @@ -203,58 +291,14 @@ describe('Components > ProjectHomePage > getDefaultPageProps', function () { }) it('should pass an error message to the error page', function () { - expect(props.title).to.equal('Workflow 2 was not found') + expect(props.title).to.equal('Workflow 3 was not found') }) }) }) describe('with the production API', function () { before(function () { - const slug = 'test-owner/test-project' - const cellect = nock('https://cellect.zooniverse.org') - .persist() - .get('/workflows/1/status') - .reply(200, { - groups: availableSubjects - }) - const scope = nock('https://www.zooniverse.org/api') - .persist() - .get('/projects') - .query(query => query.slug === slug) - .reply(200, { - projects: [PROJECT] - }) - .get('/projects') - .query(query => query.slug !== slug) - .reply(200, { - projects: [] - }) - .get('/translations') - .query(query => { - return query.translated_type === 'workflow' - && query.translated_id === '1' - && query.language === 'en' - }) - .reply(200, { - translations: [TRANSLATION] - }) - .get('/workflows') - .query(query => query.id === '1') - .reply(200, { - workflows: [WORKFLOW], - linked: { - subject_sets: [ - subjectSet('1'), - subjectSet('2'), - subjectSet('3') - ] - } - }) - .get('/workflows') - .query(query => query.id !== '1') - .reply(200, { - workflows: [] - }) + mockAPI('https://www.zooniverse.org/api') }) after(function () { @@ -287,11 +331,7 @@ describe('Components > ProjectHomePage > getDefaultPageProps', function () { grouped: false, id: '1', displayName: 'Foo', - subjectSets: [ - Object.assign(subjectSet('1'), { availableSubjects: availableSubjects[1]}), - Object.assign(subjectSet('2'), { availableSubjects: availableSubjects[2]}), - Object.assign(subjectSet('3'), { availableSubjects: availableSubjects[3]}) - ] + subjectSets: [] } ]) }) @@ -334,6 +374,43 @@ describe('Components > ProjectHomePage > getDefaultPageProps', function () { }) }) + describe('with a grouped workflow', function () { + it('should return the project\'s active workflows with subject sets', async function () { + const params = { + owner: 'test-owner', + project: 'grouped-project', + workflowID: '2' + } + const query = { + env: 'production' + } + const req = { + connection: { + encrypted: true + }, + headers: { + host: 'www.zooniverse.org' + } + } + const res = {} + const { props } = await getDefaultPageProps({ params, query, req, res }) + expect(props.workflows).to.deep.equal([ + { + completeness: 0.4, + default: true, + grouped: true, + id: '2', + displayName: 'Bar', + subjectSets: [ + Object.assign(subjectSet('1'), { availableSubjects: availableSubjects[1]}), + Object.assign(subjectSet('2'), { availableSubjects: availableSubjects[2]}), + Object.assign(subjectSet('3'), { availableSubjects: availableSubjects[3]}) + ] + } + ]) + }) + }) + describe('with an invalid workflow ID', function () { let props let res = {} @@ -342,7 +419,7 @@ describe('Components > ProjectHomePage > getDefaultPageProps', function () { const params = { owner: 'test-owner', project: 'test-project', - workflowID: '2' + workflowID: '3' } const query = { env: 'production' @@ -368,7 +445,7 @@ describe('Components > ProjectHomePage > getDefaultPageProps', function () { }) it('should pass an error message to the error page', function () { - expect(props.title).to.equal('Workflow 2 was not found') + expect(props.title).to.equal('Workflow 3 was not found') }) }) }) diff --git a/packages/app-project/src/helpers/getStaticPageProps/getStaticPageProps.spec.js b/packages/app-project/src/helpers/getStaticPageProps/getStaticPageProps.spec.js index a4a4d4dfce..c6da1e59cd 100644 --- a/packages/app-project/src/helpers/getStaticPageProps/getStaticPageProps.spec.js +++ b/packages/app-project/src/helpers/getStaticPageProps/getStaticPageProps.spec.js @@ -13,6 +13,16 @@ describe('Helpers > getStaticPageProps', function () { } } + const GROUPED_PROJECT = { + id: '2', + default_workflow: '2', + primary_language: 'en', + slug: 'test-owner/grouped-project', + links: { + active_workflows: ['2'] + } + } + const TRANSLATION = { translated_id: 1, strings: { @@ -20,6 +30,13 @@ describe('Helpers > getStaticPageProps', function () { } } + const GROUPED_TRANSLATION = { + translated_id: 2, + strings: { + display_name: 'Bar' + } + } + const WORKFLOW = { id: '1', completeness: 0.4, @@ -29,6 +46,15 @@ describe('Helpers > getStaticPageProps', function () { } } + const GROUPED_WORKFLOW = { + id: '2', + completeness: 0.4, + grouped: true, + links: { + subject_sets: ['1', '2', '3'] + } + } + const availableSubjects = { 1: 4, 2: 10, @@ -43,53 +69,82 @@ describe('Helpers > getStaticPageProps', function () { } } - describe('with the staging API', function () { - before(function () { - const slug = 'test-owner/test-project' - const cellect = nock('https://cellect.zooniverse.org') + function mockAPI(panoptesHost) { + const cellect = nock('https://cellect.zooniverse.org') + .persist() + .get('/workflows/2/status') + .reply(200, { + groups: availableSubjects + }) + const scope = nock(panoptesHost) .persist() - .get('/workflows/1/status') + .get('/projects') + .query(query => query.slug === 'test-owner/test-project') .reply(200, { - groups: availableSubjects + projects: [PROJECT] }) - const scope = nock('https://panoptes-staging.zooniverse.org/api') - .persist() - .get('/projects') - .query(query => query.slug === slug) - .reply(200, { - projects: [PROJECT] - }) - .get('/projects') - .query(query => query.slug !== slug) - .reply(200, { - projects: [] - }) - .get('/translations') - .query(query => { - return query.translated_type === 'workflow' - && query.translated_id === '1' - && query.language === 'en' - }) - .reply(200, { - translations: [TRANSLATION] - }) - .get('/workflows') - .query(query => query.id === '1') - .reply(200, { - workflows: [WORKFLOW], - linked: { - subject_sets: [ - subjectSet('1'), - subjectSet('2'), - subjectSet('3') - ] - } - }) - .get('/workflows') - .query(query => query.id !== '1') - .reply(200, { - workflows: [] - }) + .get('/projects') + .query(query => query.slug === 'test-owner/grouped-project') + .reply(200, { + projects: [GROUPED_PROJECT] + }) + .get('/projects') + .query(query => query.slug === 'test-owner/test-wrong-project') + .reply(200, { + projects: [] + }) + .get('/translations') + .query(query => { + return query.translated_type === 'workflow' + && query.translated_id === '1' + && query.language === 'en' + }) + .reply(200, { + translations: [TRANSLATION] + }) + .get('/translations') + .query(query => { + return query.translated_type === 'workflow' + && query.translated_id === '2' + && query.language === 'en' + }) + .reply(200, { + translations: [GROUPED_TRANSLATION] + }) + .get('/workflows') + .query(query => query.id === '1') + .reply(200, { + workflows: [WORKFLOW], + linked: { + subject_sets: [ + subjectSet('1'), + subjectSet('2'), + subjectSet('3') + ] + } + }) + .get('/workflows') + .query(query => query.id === '2') + .reply(200, { + workflows: [GROUPED_WORKFLOW], + linked: { + subject_sets: [ + subjectSet('1'), + subjectSet('2'), + subjectSet('3') + ] + } + }) + .get('/workflows') + .query(query => parseInt(query.id) > 2) + .reply(200, { + workflows: [] + }) + } + + describe('with the staging API', function () { + before(function () { + mockAPI('https://panoptes-staging.zooniverse.org/api') }) after(function () { @@ -113,11 +168,7 @@ describe('Helpers > getStaticPageProps', function () { grouped: false, id: '1', displayName: 'Foo', - subjectSets: [ - Object.assign(subjectSet('1'), { availableSubjects: availableSubjects[1]}), - Object.assign(subjectSet('2'), { availableSubjects: availableSubjects[2]}), - Object.assign(subjectSet('3'), { availableSubjects: availableSubjects[3]}) - ] + subjectSets: [] } ]) }) @@ -147,6 +198,43 @@ describe('Helpers > getStaticPageProps', function () { }) }) + describe('with a grouped workflow', function () { + it('should return the project\'s active workflows with subject sets', async function () { + const params = { + owner: 'test-owner', + project: 'grouped-project', + workflowID: '2' + } + const query = { + env: 'staging' + } + const req = { + connection: { + encrypted: true + }, + headers: { + host: 'www.zooniverse.org' + } + } + const res = {} + const { props } = await getStaticPageProps({ params, query }) + expect(props.workflows).to.deep.equal([ + { + completeness: 0.4, + default: true, + grouped: true, + id: '2', + displayName: 'Bar', + subjectSets: [ + Object.assign(subjectSet('1'), { availableSubjects: availableSubjects[1]}), + Object.assign(subjectSet('2'), { availableSubjects: availableSubjects[2]}), + Object.assign(subjectSet('3'), { availableSubjects: availableSubjects[3]}) + ] + } + ]) + }) + }) + describe('with an invalid workflow ID', function () { let props @@ -154,7 +242,7 @@ describe('Helpers > getStaticPageProps', function () { const params = { owner: 'test-owner', project: 'test-project', - workflowID: '2' + workflowID: '3' } const query = { env: 'staging' @@ -168,58 +256,14 @@ describe('Helpers > getStaticPageProps', function () { }) it('should return a workflow error message', function () { - expect(props.title).to.equal('Workflow 2 was not found') + expect(props.title).to.equal('Workflow 3 was not found') }) }) }) describe('with the production API', function () { before(function () { - const slug = 'test-owner/test-project' - const cellect = nock('https://cellect.zooniverse.org') - .persist() - .get('/workflows/1/status') - .reply(200, { - groups: availableSubjects - }) - const scope = nock('https://www.zooniverse.org/api') - .persist() - .get('/projects') - .query(query => query.slug === slug) - .reply(200, { - projects: [PROJECT] - }) - .get('/projects') - .query(query => query.slug !== slug) - .reply(200, { - projects: [] - }) - .get('/translations') - .query(query => { - return query.translated_type === 'workflow' - && query.translated_id === '1' - && query.language === 'en' - }) - .reply(200, { - translations: [TRANSLATION] - }) - .get('/workflows') - .query(query => query.id === '1') - .reply(200, { - workflows: [WORKFLOW], - linked: { - subject_sets: [ - subjectSet('1'), - subjectSet('2'), - subjectSet('3') - ] - } - }) - .get('/workflows') - .query(query => query.id !== '1') - .reply(200, { - workflows: [] - }) + mockAPI('https://www.zooniverse.org/api') }) after(function () { @@ -243,11 +287,7 @@ describe('Helpers > getStaticPageProps', function () { grouped: false, id: '1', displayName: 'Foo', - subjectSets: [ - Object.assign(subjectSet('1'), { availableSubjects: availableSubjects[1]}), - Object.assign(subjectSet('2'), { availableSubjects: availableSubjects[2]}), - Object.assign(subjectSet('3'), { availableSubjects: availableSubjects[3]}) - ] + subjectSets: [] } ]) }) @@ -285,6 +325,43 @@ describe('Helpers > getStaticPageProps', function () { }) }) + describe('with a grouped workflow', function () { + it('should return the project\'s active workflows with subject sets', async function () { + const params = { + owner: 'test-owner', + project: 'grouped-project', + workflowID: '2' + } + const query = { + env: 'production' + } + const req = { + connection: { + encrypted: true + }, + headers: { + host: 'www.zooniverse.org' + } + } + const res = {} + const { props } = await getStaticPageProps({ params, query }) + expect(props.workflows).to.deep.equal([ + { + completeness: 0.4, + default: true, + grouped: true, + id: '2', + displayName: 'Bar', + subjectSets: [ + Object.assign(subjectSet('1'), { availableSubjects: availableSubjects[1]}), + Object.assign(subjectSet('2'), { availableSubjects: availableSubjects[2]}), + Object.assign(subjectSet('3'), { availableSubjects: availableSubjects[3]}) + ] + } + ]) + }) + }) + describe('with an invalid workflow ID', function () { let props