diff --git a/MINT.postman_collection.json b/MINT.postman_collection.json index cc710ba3c4..4ac6bd1f56 100644 --- a/MINT.postman_collection.json +++ b/MINT.postman_collection.json @@ -1,9 +1,9 @@ { "info": { - "_postman_id": "39fb2692-2b04-47d3-9a06-6c12f8d779fd", + "_postman_id": "e2e558ab-89c3-422c-b990-cf47b083cf7a", "name": "MINT", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", - "_exporter_id": "20320964" + "_exporter_id": "20435042" }, "item": [ { @@ -513,7 +513,7 @@ "body": { "mode": "graphql", "graphql": { - "query": "mutation createPlanDiscussion ($input: PlanDiscussionCreateInput!) {\n createPlanDiscussion (input: $input) {\n id\n modelPlanID\n content\n userRole\n userRoleDescription\n status\n isAssessment\n createdBy\n createdDts\n modifiedBy\n modifiedDts\n createdByUserAccount \n {\n commonName\n } \n modifiedByUserAccount\n {\n commonName\n } \n }\n}", + "query": "mutation createPlanDiscussion ($input: PlanDiscussionCreateInput!) {\n createPlanDiscussion (input: $input) {\n id\n modelPlanID\n content\n userRole\n userRoleDescription\n isAssessment\n createdBy\n createdDts\n modifiedBy\n modifiedDts\n createdByUserAccount \n {\n commonName\n } \n modifiedByUserAccount\n {\n commonName\n } \n }\n}", "variables": "{\n \"input\": {\n \"modelPlanID\": \"{{modelPlanID}}\",\n \"content\": \"This is a question\",\n \"userRole\": \"NONE_OF_THE_ABOVE\",\n \"userRoleDescription\": \"this is a test\"\n }\n}" } }, @@ -534,8 +534,8 @@ "body": { "mode": "graphql", "graphql": { - "query": "mutation updatePlanDiscussion ($id: UUID!, $changes: PlanDiscussionChanges!) {\n updatePlanDiscussion (id: $id, changes: $changes) {\n id\n modelPlanID\n content\n userRole\n userRoleDescription\n status\n isAssessment\n createdBy\n createdDts\n modifiedBy\n modifiedDts\n createdByUserAccount \n {\n commonName\n } \n modifiedByUserAccount\n {\n commonName\n } \n }\n}", - "variables": "{\n \"id\": \"{{discussionID}}\",\n \"changes\": {\n \"content\": \"Great Changes\",\n \"userRole\": \"none_of_the_above\",\n \"status\": \"UNANSWERED\"\n }\n}" + "query": "mutation updatePlanDiscussion ($id: UUID!, $changes: PlanDiscussionChanges!) {\n updatePlanDiscussion (id: $id, changes: $changes) {\n id\n modelPlanID\n content\n userRole\n userRoleDescription\n isAssessment\n createdBy\n createdDts\n modifiedBy\n modifiedDts\n createdByUserAccount \n {\n commonName\n } \n modifiedByUserAccount\n {\n commonName\n } \n }\n}", + "variables": "{\n \"id\": \"{{discussionID}}\",\n \"changes\": {\n \"content\": \"Great Changes\",\n \"userRole\": \"none_of_the_above\"\n }\n}" } }, "url": { @@ -555,7 +555,7 @@ "body": { "mode": "graphql", "graphql": { - "query": "mutation deletePlanDiscussion ($id: UUID!) {\n deletePlanDiscussion (id: $id) {\n id\n modelPlanID\n content\n userRole\n userRoleDescription\n status\n isAssessment\n createdBy\n createdDts\n modifiedBy\n modifiedDts\n createdByUserAccount \n {\n commonName\n } \n modifiedByUserAccount\n {\n commonName\n } \n }\n}", + "query": "mutation deletePlanDiscussion ($id: UUID!) {\n deletePlanDiscussion (id: $id) {\n id\n modelPlanID\n content\n userRole\n userRoleDescription\n isAssessment\n createdBy\n createdDts\n modifiedBy\n modifiedDts\n createdByUserAccount \n {\n commonName\n } \n modifiedByUserAccount\n {\n commonName\n } \n }\n}", "variables": "{\n \"id\": \"{{discussionID}}\"\n}" } }, @@ -592,8 +592,8 @@ "body": { "mode": "graphql", "graphql": { - "query": "mutation createDiscussionReply ($input: DiscussionReplyCreateInput!) {\n createDiscussionReply (input: $input) {\n id\n discussionID\n content\n resolution\n isAssessment\n createdBy\n createdDts\n modifiedBy\n modifiedDts\n createdByUserAccount \n {\n commonName\n } \n modifiedByUserAccount\n {\n commonName\n } \n }\n}", - "variables": "{\n \"input\": {\n \"discussionID\": \"{{discussionID}}\",\n \"content\": \"This is not a resolution\",\n \"resolution\": false,\n \"userRole\": \"NONE_OF_THE_ABOVE\",\n \"userRoleDescription\": \"this is a test\"\n }\n}" + "query": "mutation createDiscussionReply ($input: DiscussionReplyCreateInput!) {\n createDiscussionReply (input: $input) {\n id\n discussionID\n content\n isAssessment\n createdBy\n createdDts\n modifiedBy\n modifiedDts\n createdByUserAccount \n {\n commonName\n } \n modifiedByUserAccount\n {\n commonName\n } \n }\n}", + "variables": "{\n \"input\": {\n \"discussionID\": \"{{discussionID}}\",\n \"content\": \"This is not a resolution\",\n \"userRole\": \"NONE_OF_THE_ABOVE\",\n \"userRoleDescription\": \"this is a test\"\n }\n}" } }, "url": { @@ -613,8 +613,8 @@ "body": { "mode": "graphql", "graphql": { - "query": "mutation updateDiscussionReply ($id: UUID!, $changes: DiscussionReplyChanges!) {\n updateDiscussionReply (id: $id, changes: $changes) {\n id\n discussionID\n content\n userRole\n userRoleDescription\n resolution\n isAssessment\n createdBy\n createdDts\n modifiedBy\n modifiedDts\n createdByUserAccount \n {\n commonName\n } \n modifiedByUserAccount\n {\n commonName\n } \n }\n}", - "variables": "{\n \"id\": \"{{discussionReplyID}}\",\n \"changes\": {\n \"content\": \"This is a resolution\",\n \"resolution\": true,\n \"userRole\": \"NONE_OF_THE_ABOVE\",\n \"userRoleDescription\": \"this is a test\"\n }\n}" + "query": "mutation updateDiscussionReply ($id: UUID!, $changes: DiscussionReplyChanges!) {\n updateDiscussionReply (id: $id, changes: $changes) {\n id\n discussionID\n content\n userRole\n userRoleDescription\n isAssessment\n createdBy\n createdDts\n modifiedBy\n modifiedDts\n createdByUserAccount \n {\n commonName\n } \n modifiedByUserAccount\n {\n commonName\n } \n }\n}", + "variables": "{\n \"id\": \"{{discussionReplyID}}\",\n \"changes\": {\n \"content\": \"This is a resolution\",\n \"userRole\": \"NONE_OF_THE_ABOVE\",\n \"userRoleDescription\": \"this is a test\"\n }\n}" } }, "url": { @@ -634,7 +634,7 @@ "body": { "mode": "graphql", "graphql": { - "query": "mutation deleteDiscussionReply ($id: UUID!) {\n deleteDiscussionReply (id: $id) {\n id\n discussionID\n content\n userRole\n userRoleDescription\n resolution\n isAssessment\n createdBy\n createdDts\n modifiedBy\n modifiedDts\n createdByUserAccount \n {\n commonName\n } \n modifiedByUserAccount\n {\n commonName\n } \n }\n}", + "query": "mutation deleteDiscussionReply ($id: UUID!) {\n deleteDiscussionReply (id: $id) {\n id\n discussionID\n content\n userRole\n userRoleDescription\n isAssessment\n createdBy\n createdDts\n modifiedBy\n modifiedDts\n createdByUserAccount \n {\n commonName\n } \n modifiedByUserAccount\n {\n commonName\n } \n }\n}", "variables": "{\n \"id\": \"{{discussionReplyID}}\"\n}" } }, diff --git a/cypress/e2e/discussions.spec.js b/cypress/e2e/discussions.spec.js index bb5adbe5ae..7b6f8bfa71 100644 --- a/cypress/e2e/discussions.spec.js +++ b/cypress/e2e/discussions.spec.js @@ -6,11 +6,11 @@ describe('Discussion Center', () => { it('asks a question and answers a question', () => { cy.clickPlanTableByName('Empty Plan'); - cy.contains('button', 'Ask a question').click(); + cy.contains('button', 'Start a discussion').click(); - cy.contains('h1', 'Ask a question'); + cy.contains('h1', 'Start a discussion'); - cy.contains('button', 'Save question').should('be.disabled'); + cy.contains('button', 'Save discussion').should('be.disabled'); cy.get('#user-role').should('not.be.disabled'); @@ -24,22 +24,22 @@ describe('Discussion Center', () => { .type('How to I get to model characteristics?') .should('have.value', 'How to I get to model characteristics?'); - cy.contains('button', 'Save question').click(); + cy.contains('button', 'Save discussion').click(); - cy.contains('button', '1 unanswered question'); + cy.contains('button', '1 new discussion topic'); cy.contains( '.usa-alert__body', - 'There are no answered questions yet. When a question is answered, it will appear here with the response.' + 'There are no discussions with replies yet. Once a discussion has been replied to, it will appear here.' ); - cy.contains('button', 'Answer').click(); + cy.contains('button', 'Reply').click(); cy.contains('p', 'How to I get to model characteristics?'); - cy.contains('label', 'Type your answer'); + cy.contains('label', 'Type your reply'); - cy.contains('button', 'Save answer').should('be.disabled'); + cy.contains('button', 'Save reply').should('be.disabled'); cy.get('#discussion-content') .should('not.be.disabled') @@ -49,13 +49,13 @@ describe('Discussion Center', () => { 'Model characteristics is located within the task list.' ); - cy.contains('button', 'Save answer').click(); + cy.contains('button', 'Save reply').click(); - cy.contains('button', '1 answered question'); + cy.contains('button', '1 discussion'); cy.contains( '.usa-alert__body', - 'There are no unanswered questions. Ask a question using the link above.' + 'There are no new discussion topics. Start a discussion and it will appear here.' ); cy.get('[data-testid="close-discussions"]').click(); diff --git a/cypress/e2e/modelPlan.spec.js b/cypress/e2e/modelPlan.spec.js index 5ccaa7e276..bc632d1e51 100644 --- a/cypress/e2e/modelPlan.spec.js +++ b/cypress/e2e/modelPlan.spec.js @@ -35,7 +35,7 @@ describe('The Model Plan Form', () => { cy.contains('h3', 'Model basics'); - cy.contains('button', 'Start').click(); + cy.contains('button', /Start$/).click(); cy.location().should(loc => { expect(loc.pathname).to.match(/\/models\/.{36}\/task-list\/basics/); diff --git a/migrations/V105__Drop_Discussion_Status.sql b/migrations/V105__Drop_Discussion_Status.sql new file mode 100644 index 0000000000..0550c53269 --- /dev/null +++ b/migrations/V105__Drop_Discussion_Status.sql @@ -0,0 +1,4 @@ +ALTER TABLE plan_discussion + DROP COLUMN status; + +DROP TYPE DISCUSSION_STATUS; diff --git a/migrations/V106__Drop_Discussion_Resolutions.sql b/migrations/V106__Drop_Discussion_Resolutions.sql new file mode 100644 index 0000000000..d0c67c242d --- /dev/null +++ b/migrations/V106__Drop_Discussion_Resolutions.sql @@ -0,0 +1,2 @@ +ALTER TABLE discussion_reply + DROP COLUMN resolution; diff --git a/pkg/graph/generated/generated.go b/pkg/graph/generated/generated.go index 969551d374..228b43d9b6 100644 --- a/pkg/graph/generated/generated.go +++ b/pkg/graph/generated/generated.go @@ -118,7 +118,6 @@ type ComplexityRoot struct { ModifiedBy func(childComplexity int) int ModifiedByUserAccount func(childComplexity int) int ModifiedDts func(childComplexity int) int - Resolution func(childComplexity int) int UserRole func(childComplexity int) int UserRoleDescription func(childComplexity int) int } @@ -440,7 +439,6 @@ type ComplexityRoot struct { ModifiedByUserAccount func(childComplexity int) int ModifiedDts func(childComplexity int) int Replies func(childComplexity int) int - Status func(childComplexity int) int UserRole func(childComplexity int) int UserRoleDescription func(childComplexity int) int } @@ -1381,13 +1379,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.DiscussionReply.ModifiedDts(childComplexity), true - case "DiscussionReply.resolution": - if e.complexity.DiscussionReply.Resolution == nil { - break - } - - return e.complexity.DiscussionReply.Resolution(childComplexity), true - case "DiscussionReply.userRole": if e.complexity.DiscussionReply.UserRole == nil { break @@ -3493,13 +3484,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.PlanDiscussion.Replies(childComplexity), true - case "PlanDiscussion.status": - if e.complexity.PlanDiscussion.Status == nil { - break - } - - return e.complexity.PlanDiscussion.Status(childComplexity), true - case "PlanDiscussion.userRole": if e.complexity.PlanDiscussion.UserRole == nil { break @@ -7067,7 +7051,6 @@ type PlanDiscussion { content: String userRole: DiscussionUserRole userRoleDescription: String - status: DiscussionStatus! replies: [DiscussionReply!]! isAssessment: Boolean! @@ -7097,7 +7080,6 @@ https://gqlgen.com/reference/changesets/ """ input PlanDiscussionChanges @goModel(model: "map[string]interface{}") { content: String - status: DiscussionStatus userRole: DiscussionUserRole userRoleDescription: String } @@ -7111,7 +7093,6 @@ type DiscussionReply { content: String userRole: DiscussionUserRole userRoleDescription: String - resolution: Boolean isAssessment: Boolean! createdBy: UUID! @@ -7130,7 +7111,6 @@ input DiscussionReplyCreateInput { content: String! userRole: DiscussionUserRole userRoleDescription: String - resolution: Boolean! = false } """ @@ -7140,7 +7120,6 @@ https://gqlgen.com/reference/changesets/ """ input DiscussionReplyChanges @goModel(model: "map[string]interface{}") { content: String - resolution: Boolean userRole: DiscussionUserRole userRoleDescription: String } @@ -8502,12 +8481,6 @@ enum CMMIGroup { TBD } -enum DiscussionStatus { - ANSWERED - WAITING_FOR_RESPONSE - UNANSWERED -} - enum DocumentType { CONCEPT_PAPER, POLICY_PAPER, @@ -11853,47 +11826,6 @@ func (ec *executionContext) fieldContext_DiscussionReply_userRoleDescription(ctx return fc, nil } -func (ec *executionContext) _DiscussionReply_resolution(ctx context.Context, field graphql.CollectedField, obj *models.DiscussionReply) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_DiscussionReply_resolution(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.Resolution, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - return graphql.Null - } - res := resTmp.(bool) - fc.Result = res - return ec.marshalOBoolean2bool(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_DiscussionReply_resolution(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "DiscussionReply", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type Boolean does not have child fields") - }, - } - return fc, nil -} - func (ec *executionContext) _DiscussionReply_isAssessment(ctx context.Context, field graphql.CollectedField, obj *models.DiscussionReply) (ret graphql.Marshaler) { fc, err := ec.fieldContext_DiscussionReply_isAssessment(ctx, field) if err != nil { @@ -15724,8 +15656,6 @@ func (ec *executionContext) fieldContext_ModelPlan_discussions(ctx context.Conte return ec.fieldContext_PlanDiscussion_userRole(ctx, field) case "userRoleDescription": return ec.fieldContext_PlanDiscussion_userRoleDescription(ctx, field) - case "status": - return ec.fieldContext_PlanDiscussion_status(ctx, field) case "replies": return ec.fieldContext_PlanDiscussion_replies(ctx, field) case "isAssessment": @@ -18404,8 +18334,6 @@ func (ec *executionContext) fieldContext_Mutation_createPlanDiscussion(ctx conte return ec.fieldContext_PlanDiscussion_userRole(ctx, field) case "userRoleDescription": return ec.fieldContext_PlanDiscussion_userRoleDescription(ctx, field) - case "status": - return ec.fieldContext_PlanDiscussion_status(ctx, field) case "replies": return ec.fieldContext_PlanDiscussion_replies(ctx, field) case "isAssessment": @@ -18513,8 +18441,6 @@ func (ec *executionContext) fieldContext_Mutation_updatePlanDiscussion(ctx conte return ec.fieldContext_PlanDiscussion_userRole(ctx, field) case "userRoleDescription": return ec.fieldContext_PlanDiscussion_userRoleDescription(ctx, field) - case "status": - return ec.fieldContext_PlanDiscussion_status(ctx, field) case "replies": return ec.fieldContext_PlanDiscussion_replies(ctx, field) case "isAssessment": @@ -18622,8 +18548,6 @@ func (ec *executionContext) fieldContext_Mutation_deletePlanDiscussion(ctx conte return ec.fieldContext_PlanDiscussion_userRole(ctx, field) case "userRoleDescription": return ec.fieldContext_PlanDiscussion_userRoleDescription(ctx, field) - case "status": - return ec.fieldContext_PlanDiscussion_status(ctx, field) case "replies": return ec.fieldContext_PlanDiscussion_replies(ctx, field) case "isAssessment": @@ -18731,8 +18655,6 @@ func (ec *executionContext) fieldContext_Mutation_createDiscussionReply(ctx cont return ec.fieldContext_DiscussionReply_userRole(ctx, field) case "userRoleDescription": return ec.fieldContext_DiscussionReply_userRoleDescription(ctx, field) - case "resolution": - return ec.fieldContext_DiscussionReply_resolution(ctx, field) case "isAssessment": return ec.fieldContext_DiscussionReply_isAssessment(ctx, field) case "createdBy": @@ -18838,8 +18760,6 @@ func (ec *executionContext) fieldContext_Mutation_updateDiscussionReply(ctx cont return ec.fieldContext_DiscussionReply_userRole(ctx, field) case "userRoleDescription": return ec.fieldContext_DiscussionReply_userRoleDescription(ctx, field) - case "resolution": - return ec.fieldContext_DiscussionReply_resolution(ctx, field) case "isAssessment": return ec.fieldContext_DiscussionReply_isAssessment(ctx, field) case "createdBy": @@ -18945,8 +18865,6 @@ func (ec *executionContext) fieldContext_Mutation_deleteDiscussionReply(ctx cont return ec.fieldContext_DiscussionReply_userRole(ctx, field) case "userRoleDescription": return ec.fieldContext_DiscussionReply_userRoleDescription(ctx, field) - case "resolution": - return ec.fieldContext_DiscussionReply_resolution(ctx, field) case "isAssessment": return ec.fieldContext_DiscussionReply_isAssessment(ctx, field) case "createdBy": @@ -28200,50 +28118,6 @@ func (ec *executionContext) fieldContext_PlanDiscussion_userRoleDescription(ctx return fc, nil } -func (ec *executionContext) _PlanDiscussion_status(ctx context.Context, field graphql.CollectedField, obj *models.PlanDiscussion) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_PlanDiscussion_status(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.Status, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } - return graphql.Null - } - res := resTmp.(models.DiscussionStatus) - fc.Result = res - return ec.marshalNDiscussionStatus2githubᚗcomᚋcmsgovᚋmintᚑappᚋpkgᚋmodelsᚐDiscussionStatus(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_PlanDiscussion_status(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "PlanDiscussion", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type DiscussionStatus does not have child fields") - }, - } - return fc, nil -} - func (ec *executionContext) _PlanDiscussion_replies(ctx context.Context, field graphql.CollectedField, obj *models.PlanDiscussion) (ret graphql.Marshaler) { fc, err := ec.fieldContext_PlanDiscussion_replies(ctx, field) if err != nil { @@ -28293,8 +28167,6 @@ func (ec *executionContext) fieldContext_PlanDiscussion_replies(ctx context.Cont return ec.fieldContext_DiscussionReply_userRole(ctx, field) case "userRoleDescription": return ec.fieldContext_DiscussionReply_userRoleDescription(ctx, field) - case "resolution": - return ec.fieldContext_DiscussionReply_resolution(ctx, field) case "isAssessment": return ec.fieldContext_DiscussionReply_isAssessment(ctx, field) case "createdBy": @@ -50415,11 +50287,7 @@ func (ec *executionContext) unmarshalInputDiscussionReplyCreateInput(ctx context asMap[k] = v } - if _, present := asMap["resolution"]; !present { - asMap["resolution"] = false - } - - fieldsInOrder := [...]string{"discussionID", "content", "userRole", "userRoleDescription", "resolution"} + fieldsInOrder := [...]string{"discussionID", "content", "userRole", "userRoleDescription"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -50462,15 +50330,6 @@ func (ec *executionContext) unmarshalInputDiscussionReplyCreateInput(ctx context return it, err } it.UserRoleDescription = data - case "resolution": - var err error - - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("resolution")) - data, err := ec.unmarshalNBoolean2bool(ctx, v) - if err != nil { - return it, err - } - it.Resolution = data } } @@ -51384,8 +51243,6 @@ func (ec *executionContext) _DiscussionReply(ctx context.Context, sel ast.Select out.Values[i] = ec._DiscussionReply_userRole(ctx, field, obj) case "userRoleDescription": out.Values[i] = ec._DiscussionReply_userRoleDescription(ctx, field, obj) - case "resolution": - out.Values[i] = ec._DiscussionReply_resolution(ctx, field, obj) case "isAssessment": out.Values[i] = ec._DiscussionReply_isAssessment(ctx, field, obj) if out.Values[i] == graphql.Null { @@ -54649,11 +54506,6 @@ func (ec *executionContext) _PlanDiscussion(ctx context.Context, sel ast.Selecti out.Values[i] = ec._PlanDiscussion_userRole(ctx, field, obj) case "userRoleDescription": out.Values[i] = ec._PlanDiscussion_userRoleDescription(ctx, field, obj) - case "status": - out.Values[i] = ec._PlanDiscussion_status(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&out.Invalids, 1) - } case "replies": field := field @@ -60748,22 +60600,6 @@ func (ec *executionContext) unmarshalNDiscussionReplyCreateInput2githubᚗcomᚋ return res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) unmarshalNDiscussionStatus2githubᚗcomᚋcmsgovᚋmintᚑappᚋpkgᚋmodelsᚐDiscussionStatus(ctx context.Context, v interface{}) (models.DiscussionStatus, error) { - tmp, err := graphql.UnmarshalString(v) - res := models.DiscussionStatus(tmp) - return res, graphql.ErrorOnPath(ctx, err) -} - -func (ec *executionContext) marshalNDiscussionStatus2githubᚗcomᚋcmsgovᚋmintᚑappᚋpkgᚋmodelsᚐDiscussionStatus(ctx context.Context, sel ast.SelectionSet, v models.DiscussionStatus) graphql.Marshaler { - res := graphql.MarshalString(string(v)) - if res == graphql.Null { - if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { - ec.Errorf(ctx, "the requested element is null which the schema does not allow") - } - } - return res -} - func (ec *executionContext) unmarshalNDiscussionUserRole2githubᚗcomᚋcmsgovᚋmintᚑappᚋpkgᚋmodelsᚐDiscussionUserRole(ctx context.Context, v interface{}) (models.DiscussionUserRole, error) { tmp, err := graphql.UnmarshalString(v) res := models.DiscussionUserRole(tmp) @@ -65249,23 +65085,6 @@ func (ec *executionContext) marshalODiscussionRoleSelection2ᚖgithubᚗcomᚋcm return ec._DiscussionRoleSelection(ctx, sel, v) } -func (ec *executionContext) unmarshalODiscussionStatus2ᚖgithubᚗcomᚋcmsgovᚋmintᚑappᚋpkgᚋmodelsᚐDiscussionStatus(ctx context.Context, v interface{}) (*models.DiscussionStatus, error) { - if v == nil { - return nil, nil - } - tmp, err := graphql.UnmarshalString(v) - res := models.DiscussionStatus(tmp) - return &res, graphql.ErrorOnPath(ctx, err) -} - -func (ec *executionContext) marshalODiscussionStatus2ᚖgithubᚗcomᚋcmsgovᚋmintᚑappᚋpkgᚋmodelsᚐDiscussionStatus(ctx context.Context, sel ast.SelectionSet, v *models.DiscussionStatus) graphql.Marshaler { - if v == nil { - return graphql.Null - } - res := graphql.MarshalString(string(*v)) - return res -} - func (ec *executionContext) unmarshalODiscussionUserRole2ᚖgithubᚗcomᚋcmsgovᚋmintᚑappᚋpkgᚋmodelsᚐDiscussionUserRole(ctx context.Context, v interface{}) (*models.DiscussionUserRole, error) { if v == nil { return nil, nil diff --git a/pkg/graph/model/models_gen.go b/pkg/graph/model/models_gen.go index 1cfccabad2..ba6a113680 100644 --- a/pkg/graph/model/models_gen.go +++ b/pkg/graph/model/models_gen.go @@ -35,7 +35,6 @@ type DiscussionReplyCreateInput struct { Content string `json:"content"` UserRole *models.DiscussionUserRole `json:"userRole,omitempty"` UserRoleDescription *string `json:"userRoleDescription,omitempty"` - Resolution bool `json:"resolution"` } // The current user's Launch Darkly key diff --git a/pkg/graph/resolvers/access_control_test.go b/pkg/graph/resolvers/access_control_test.go index 971a7f4ba7..f634a683ad 100644 --- a/pkg/graph/resolvers/access_control_test.go +++ b/pkg/graph/resolvers/access_control_test.go @@ -51,7 +51,7 @@ func (suite *ResolverSuite) TestErrorIfNotCollaborator() { // Create discussion and reply discussion := suite.createPlanDiscussion(plan, "This is a test comment") - reply := suite.createDiscussionReply(discussion, "This is a test reply", false) + reply := suite.createDiscussionReply(discussion, "This is a test reply") //5. User is collaborator by discusionID err = accesscontrol.ErrorIfNotCollaborator(reply, suite.testConfigs.Logger, basicUserPrincipal, suite.testConfigs.Store) diff --git a/pkg/graph/resolvers/auditing_paradigm_test.go b/pkg/graph/resolvers/auditing_paradigm_test.go index 4972ae491d..5b1fb5699c 100644 --- a/pkg/graph/resolvers/auditing_paradigm_test.go +++ b/pkg/graph/resolvers/auditing_paradigm_test.go @@ -19,7 +19,6 @@ func (suite *ResolverSuite) TestDeletionActorAccuracy() { input := &model.DiscussionReplyCreateInput{ DiscussionID: discussion.ID, Content: "this is a test reply", - Resolution: false, UserRole: models.DiscussionUserRolePointer(models.DiscussionRoleNoneOfTheAbove), UserRoleDescription: models.StringPointer("this is a test"), } diff --git a/pkg/graph/resolvers/plan_discussion.go b/pkg/graph/resolvers/plan_discussion.go index 453131e1b0..5dfd310220 100644 --- a/pkg/graph/resolvers/plan_discussion.go +++ b/pkg/graph/resolvers/plan_discussion.go @@ -166,7 +166,6 @@ func CreateDiscussionReply( principal.AllowASSESSMENT(), input.DiscussionID, input.Content, - input.Resolution, input.UserRole, input.UserRoleDescription, ) diff --git a/pkg/graph/resolvers/plan_discussion_test.go b/pkg/graph/resolvers/plan_discussion_test.go index 2e3c888657..1efc84d425 100644 --- a/pkg/graph/resolvers/plan_discussion_test.go +++ b/pkg/graph/resolvers/plan_discussion_test.go @@ -3,8 +3,8 @@ package resolvers import ( "context" "fmt" - "github.com/google/uuid" + _ "github.com/lib/pq" // required for postgres driver in sql "github.com/stretchr/testify/assert" "golang.org/x/sync/errgroup" @@ -39,7 +39,6 @@ func (suite *ResolverSuite) TestCreatePlanDiscussion() { suite.NotNil(result.ID) suite.EqualValues(plan.ID, result.ModelPlanID) suite.EqualValues(input.Content, result.Content) - suite.EqualValues(models.DiscussionUnAnswered, result.Status) suite.True(result.IsAssessment) // default principal for the test suite is an assessment user suite.Nil(result.ModifiedBy) suite.Nil(result.ModifiedDts) @@ -72,7 +71,6 @@ func (suite *ResolverSuite) TestCreatePlanDiscussionAsRegularUser() { suite.NotNil(result.ID) suite.EqualValues(plan.ID, result.ModelPlanID) suite.EqualValues(input.Content, result.Content) - suite.EqualValues(models.DiscussionUnAnswered, result.Status) suite.False(result.IsAssessment) suite.Nil(result.ModifiedBy) suite.Nil(result.ModifiedDts) @@ -105,7 +103,6 @@ func (suite *ResolverSuite) TestPlanDiscussionUserRole_ValidRoleNoDescription() suite.EqualValues(plan.ID, planDiscussion.ModelPlanID) suite.EqualValues(planDiscussionInput.Content, planDiscussion.Content) suite.EqualValues(planDiscussionInput.UserRole, planDiscussion.UserRole) - suite.EqualValues(models.DiscussionUnAnswered, planDiscussion.Status) suite.True(planDiscussion.IsAssessment) // default principal for the test suite is an assessment user suite.Nil(planDiscussion.ModifiedBy) suite.Nil(planDiscussion.ModifiedDts) @@ -167,14 +164,12 @@ func (suite *ResolverSuite) TestUpdatePlanDiscussion() { changes := map[string]interface{}{ "content": "This is now updated! Thanks for looking at my test", - "status": models.DiscussionAnswered, } result, err := UpdatePlanDiscussion(suite.testConfigs.Logger, discussion.ID, changes, suite.testConfigs.Principal, suite.testConfigs.Store) suite.NoError(err) suite.EqualValues(discussion.ID, result.ID) suite.EqualValues(changes["content"], result.Content) - suite.EqualValues(changes["status"], result.Status) suite.EqualValues(suite.testConfigs.Principal.UserAccount.ID, result.CreatedBy) suite.EqualValues(suite.testConfigs.Principal.UserAccount.ID, *result.ModifiedBy) } @@ -196,7 +191,7 @@ func (suite *ResolverSuite) TestDeletePlanDiscussion() { func (suite *ResolverSuite) TestDeletePlanDiscussionWithReply() { plan := suite.createModelPlan("Test Plan") discussion := suite.createPlanDiscussion(plan, "This is a test comment") - _ = suite.createDiscussionReply(discussion, "This is a test reply", false) + _ = suite.createDiscussionReply(discussion, "This is a test reply") _, err := DeletePlanDiscussion(suite.testConfigs.Logger, discussion.ID, suite.testConfigs.Principal, suite.testConfigs.Store) suite.Error(err) @@ -210,7 +205,6 @@ func (suite *ResolverSuite) TestCreateDiscussionReply() { input := &model.DiscussionReplyCreateInput{ DiscussionID: discussion.ID, Content: "This is a test reply", - Resolution: true, UserRole: models.DiscussionUserRolePointer(models.DiscussionRoleNoneOfTheAbove), UserRoleDescription: models.StringPointer("this is a test"), } @@ -220,7 +214,6 @@ func (suite *ResolverSuite) TestCreateDiscussionReply() { suite.NotNil(result.ID) suite.EqualValues(discussion.ID, result.DiscussionID) suite.EqualValues(input.Content, result.Content) - suite.EqualValues(input.Resolution, result.Resolution) suite.True(result.IsAssessment) // default principal for the test suite is an assessment user } @@ -231,7 +224,6 @@ func (suite *ResolverSuite) TestCreateDiscussionReplyAsRegularUser() { input := &model.DiscussionReplyCreateInput{ DiscussionID: discussion.ID, Content: "This is a test reply", - Resolution: true, UserRole: models.DiscussionUserRolePointer(models.DiscussionRoleNoneOfTheAbove), UserRoleDescription: models.StringPointer("this is a test"), } @@ -244,27 +236,24 @@ func (suite *ResolverSuite) TestCreateDiscussionReplyAsRegularUser() { suite.NotNil(result.ID) suite.EqualValues(discussion.ID, result.DiscussionID) suite.EqualValues(input.Content, result.Content) - suite.EqualValues(input.Resolution, result.Resolution) suite.False(result.IsAssessment) } func (suite *ResolverSuite) TestUpdateDiscussionReply() { plan := suite.createModelPlan("Test Plan") discussion := suite.createPlanDiscussion(plan, "This is a test comment") - reply := suite.createDiscussionReply(discussion, "This is a test reply", false) + reply := suite.createDiscussionReply(discussion, "This is a test reply") assert.Nil(suite.T(), reply.ModifiedBy) assert.Nil(suite.T(), reply.ModifiedDts) changes := map[string]interface{}{ - "content": "This is now updated! Thanks for looking at my test", - "resolution": true, + "content": "This is now updated! Thanks for looking at my test", } result, err := UpdateDiscussionReply(suite.testConfigs.Logger, reply.ID, changes, suite.testConfigs.Principal, suite.testConfigs.Store) suite.NoError(err) suite.EqualValues(changes["content"], result.Content) - suite.EqualValues(changes["resolution"], result.Resolution) suite.EqualValues(suite.testConfigs.Principal.UserAccount.ID, result.CreatedBy) suite.EqualValues(suite.testConfigs.Principal.UserAccount.ID, *result.ModifiedBy) } @@ -272,8 +261,8 @@ func (suite *ResolverSuite) TestUpdateDiscussionReply() { func (suite *ResolverSuite) TestDiscussionReplyCollectionByDiscusionID() { plan := suite.createModelPlan("Test Plan") discussion := suite.createPlanDiscussion(plan, "This is a test comment") - _ = suite.createDiscussionReply(discussion, "This is a test reply", false) - _ = suite.createDiscussionReply(discussion, "This is another test reply", true) + _ = suite.createDiscussionReply(discussion, "This is a test reply") + _ = suite.createDiscussionReply(discussion, "This is another test reply") result, err := DiscussionReplyCollectionByDiscusionIDLOADER(suite.testConfigs.Context, discussion.ID) suite.NoError(err) @@ -281,9 +270,9 @@ func (suite *ResolverSuite) TestDiscussionReplyCollectionByDiscusionID() { // Check that adding another dicussion doesn't affect the first one discussionTwo := suite.createPlanDiscussion(plan, "This is another test comment") - _ = suite.createDiscussionReply(discussionTwo, "This is a test reply", false) - _ = suite.createDiscussionReply(discussionTwo, "This is another test reply", true) - _ = suite.createDiscussionReply(discussionTwo, "This is a third test reply", true) + _ = suite.createDiscussionReply(discussionTwo, "This is a test reply") + _ = suite.createDiscussionReply(discussionTwo, "This is another test reply") + _ = suite.createDiscussionReply(discussionTwo, "This is a third test reply") // Assert the count on the _first_ discussion is still 2 result, err = DiscussionReplyCollectionByDiscusionIDLOADER(suite.testConfigs.Context, discussion.ID) @@ -294,8 +283,8 @@ func (suite *ResolverSuite) TestDiscussionReplyCollectionByDiscusionID() { func (suite *ResolverSuite) TestPlanDiscussionCollectionByModelPlanID() { plan := suite.createModelPlan("Test Plan") discussion := suite.createPlanDiscussion(plan, "This is a test comment") - _ = suite.createDiscussionReply(discussion, "This is a test reply", false) - _ = suite.createDiscussionReply(discussion, "This is another test reply", true) + _ = suite.createDiscussionReply(discussion, "This is a test reply") + _ = suite.createDiscussionReply(discussion, "This is another test reply") result, err := PlanDiscussionGetByModelPlanIDLOADER(suite.testConfigs.Context, plan.ID) suite.NoError(err) @@ -303,9 +292,9 @@ func (suite *ResolverSuite) TestPlanDiscussionCollectionByModelPlanID() { // Check that adding another dicussion doesn't affect the first one discussionTwo := suite.createPlanDiscussion(plan, "This is another test comment") - _ = suite.createDiscussionReply(discussionTwo, "This is a test reply", false) - _ = suite.createDiscussionReply(discussionTwo, "This is another test reply", true) - _ = suite.createDiscussionReply(discussionTwo, "This is a third test reply", true) + _ = suite.createDiscussionReply(discussionTwo, "This is a test reply") + _ = suite.createDiscussionReply(discussionTwo, "This is another test reply") + _ = suite.createDiscussionReply(discussionTwo, "This is a third test reply") // Assert the count on the is now 2 after adding another discussion result, err = PlanDiscussionGetByModelPlanIDLOADER(suite.testConfigs.Context, plan.ID) @@ -316,8 +305,8 @@ func (suite *ResolverSuite) TestPlanDiscussionCollectionByModelPlanID() { func (suite *ResolverSuite) TestDeleteDiscussionReply() { plan := suite.createModelPlan("Test Plan") discussion := suite.createPlanDiscussion(plan, "This is a test comment") - reply := suite.createDiscussionReply(discussion, "This is a test reply", false) - _ = suite.createDiscussionReply(discussion, "This is another test reply", false) + reply := suite.createDiscussionReply(discussion, "This is a test reply") + _ = suite.createDiscussionReply(discussion, "This is another test reply") _, err := DeleteDiscussionReply(suite.testConfigs.Logger, reply.ID, suite.testConfigs.Principal, suite.testConfigs.Store) suite.NoError(err) @@ -366,12 +355,12 @@ func verifyPlanDiscussionLoader(ctx context.Context, modelPlanID uuid.UUID) erro func (suite *ResolverSuite) TestDiscussionReplyDataLoader() { plan1 := suite.createModelPlan("Plan For DiscR 1") discussion1 := suite.createPlanDiscussion(plan1, "This is a test comment") - _ = suite.createDiscussionReply(discussion1, "This is a test reply", false) - _ = suite.createDiscussionReply(discussion1, "This is another test reply", true) + _ = suite.createDiscussionReply(discussion1, "This is a test reply") + _ = suite.createDiscussionReply(discussion1, "This is another test reply") plan2 := suite.createModelPlan("Plan For DiscR 2") discussion2 := suite.createPlanDiscussion(plan2, "This is a test comment") - _ = suite.createDiscussionReply(discussion2, "This is a test reply", false) - _ = suite.createDiscussionReply(discussion2, "This is another test reply", true) + _ = suite.createDiscussionReply(discussion2, "This is a test reply") + _ = suite.createDiscussionReply(discussion2, "This is another test reply") g, ctx := errgroup.WithContext(suite.testConfigs.Context) g.Go(func() error { diff --git a/pkg/graph/resolvers/resolver_test.go b/pkg/graph/resolvers/resolver_test.go index d49899230a..2559010d47 100644 --- a/pkg/graph/resolvers/resolver_test.go +++ b/pkg/graph/resolvers/resolver_test.go @@ -83,12 +83,10 @@ func (suite *ResolverSuite) createPlanDiscussion(mp *models.ModelPlan, content s func (suite *ResolverSuite) createDiscussionReply( pd *models.PlanDiscussion, content string, - resolution bool, ) *models.DiscussionReply { input := &model.DiscussionReplyCreateInput{ DiscussionID: pd.ID, Content: content, - Resolution: resolution, UserRole: models.DiscussionUserRolePointer(models.DiscussionRoleNoneOfTheAbove), UserRoleDescription: models.StringPointer("this is a test"), } diff --git a/pkg/graph/schema.graphql b/pkg/graph/schema.graphql index 71377a025f..a38dce4a60 100644 --- a/pkg/graph/schema.graphql +++ b/pkg/graph/schema.graphql @@ -394,7 +394,6 @@ type PlanDiscussion { content: String userRole: DiscussionUserRole userRoleDescription: String - status: DiscussionStatus! replies: [DiscussionReply!]! isAssessment: Boolean! @@ -424,7 +423,6 @@ https://gqlgen.com/reference/changesets/ """ input PlanDiscussionChanges @goModel(model: "map[string]interface{}") { content: String - status: DiscussionStatus userRole: DiscussionUserRole userRoleDescription: String } @@ -438,7 +436,6 @@ type DiscussionReply { content: String userRole: DiscussionUserRole userRoleDescription: String - resolution: Boolean isAssessment: Boolean! createdBy: UUID! @@ -457,7 +454,6 @@ input DiscussionReplyCreateInput { content: String! userRole: DiscussionUserRole userRoleDescription: String - resolution: Boolean! = false } """ @@ -467,7 +463,6 @@ https://gqlgen.com/reference/changesets/ """ input DiscussionReplyChanges @goModel(model: "map[string]interface{}") { content: String - resolution: Boolean userRole: DiscussionUserRole userRoleDescription: String } @@ -1829,12 +1824,6 @@ enum CMMIGroup { TBD } -enum DiscussionStatus { - ANSWERED - WAITING_FOR_RESPONSE - UNANSWERED -} - enum DocumentType { CONCEPT_PAPER, POLICY_PAPER, diff --git a/pkg/models/plan_discussion.go b/pkg/models/plan_discussion.go index c616c7dcbf..314823c8d0 100644 --- a/pkg/models/plan_discussion.go +++ b/pkg/models/plan_discussion.go @@ -11,7 +11,6 @@ type PlanDiscussion struct { Content string `json:"content" db:"content"` UserRole *DiscussionUserRole `json:"userRole" db:"user_role"` UserRoleDescription *string `json:"userRoleDescription" db:"user_role_description"` - Status DiscussionStatus `json:"status" db:"status"` IsAssessment bool `json:"isAssessment" db:"is_assessment"` } @@ -28,7 +27,6 @@ func NewPlanDiscussion( Content: content, UserRole: userRole, UserRoleDescription: userRoleDescription, - Status: DiscussionUnAnswered, IsAssessment: isAssessment, modelPlanRelation: NewModelPlanRelation(modelPlanID), baseStruct: NewBaseStruct(principal), @@ -42,7 +40,6 @@ type DiscussionReply struct { Content string `json:"content" db:"content"` UserRole *DiscussionUserRole `json:"userRole" db:"user_role"` UserRoleDescription *string `json:"userRoleDescription" db:"user_role_description"` - Resolution bool `json:"resolution" db:"resolution"` //default to false IsAssessment bool `json:"isAssessment" db:"is_assessment"` } @@ -52,7 +49,6 @@ func NewDiscussionReply( isAssessment bool, discussionID uuid.UUID, content string, - resolution bool, userRole *DiscussionUserRole, userRoleDescription *string, ) *DiscussionReply { @@ -60,23 +56,12 @@ func NewDiscussionReply( Content: content, UserRole: userRole, UserRoleDescription: userRoleDescription, - Resolution: resolution, IsAssessment: isAssessment, discussionRelation: NewDiscussionRelation(discussionID), baseStruct: NewBaseStruct(principal), } } -// DiscussionStatus is an enum that represents the status of a Discussion -type DiscussionStatus string - -// These constants represent the possible values of a DiscussionStatus -const ( - DiscussionAnswered DiscussionStatus = "ANSWERED" - DiscussionWaiting DiscussionStatus = "WAITING_FOR_RESPONSE" - DiscussionUnAnswered DiscussionStatus = "UNANSWERED" -) - // DiscussionUserRole is an enum that represents the role of a user in a Discussion type DiscussionUserRole string diff --git a/pkg/storage/SQL/discussion_reply/create.sql b/pkg/storage/SQL/discussion_reply/create.sql index 787af1143a..954b179c23 100644 --- a/pkg/storage/SQL/discussion_reply/create.sql +++ b/pkg/storage/SQL/discussion_reply/create.sql @@ -4,7 +4,6 @@ INSERT INTO discussion_reply( content, user_role, user_role_description, - resolution, is_assessment, created_by, modified_by @@ -15,7 +14,6 @@ VALUES ( :content, :user_role, :user_role_description, - :resolution, :is_assessment, :created_by, :modified_by @@ -25,7 +23,6 @@ discussion_id, content, user_role, user_role_description, -resolution, is_assessment, created_by, created_dts, diff --git a/pkg/storage/SQL/discussion_reply/delete.sql b/pkg/storage/SQL/discussion_reply/delete.sql index 69c7b104c4..425b7f618c 100644 --- a/pkg/storage/SQL/discussion_reply/delete.sql +++ b/pkg/storage/SQL/discussion_reply/delete.sql @@ -5,7 +5,6 @@ discussion_id, content, user_role, user_role_description, -resolution, is_assessment, created_by, created_dts, diff --git a/pkg/storage/SQL/discussion_reply/get_by_discussion_id_LOADER.sql b/pkg/storage/SQL/discussion_reply/get_by_discussion_id_LOADER.sql index f0c291cd74..c79f8875f2 100644 --- a/pkg/storage/SQL/discussion_reply/get_by_discussion_id_LOADER.sql +++ b/pkg/storage/SQL/discussion_reply/get_by_discussion_id_LOADER.sql @@ -12,7 +12,6 @@ SELECT discR.content, discR.user_role, discR.user_role_description, - discR.resolution, discR.is_assessment, discR.created_by, discR.created_dts, diff --git a/pkg/storage/SQL/discussion_reply/get_by_id.sql b/pkg/storage/SQL/discussion_reply/get_by_id.sql index 7c28222d08..81618f0431 100644 --- a/pkg/storage/SQL/discussion_reply/get_by_id.sql +++ b/pkg/storage/SQL/discussion_reply/get_by_id.sql @@ -4,7 +4,6 @@ SELECT content, user_role, user_role_description, - resolution, is_assessment, created_by, created_dts, diff --git a/pkg/storage/SQL/discussion_reply/update.sql b/pkg/storage/SQL/discussion_reply/update.sql index 93d83c987a..23cac52c1a 100644 --- a/pkg/storage/SQL/discussion_reply/update.sql +++ b/pkg/storage/SQL/discussion_reply/update.sql @@ -4,7 +4,6 @@ SET content = :content, user_role = :user_role, user_role_description = :user_role_description, - resolution = :resolution, modified_by = :modified_by, modified_dts = CURRENT_TIMESTAMP WHERE id = :id @@ -14,7 +13,6 @@ discussion_id, content, user_role, user_role_description, -resolution, is_assessment, created_by, created_dts, diff --git a/pkg/storage/SQL/plan_discussion/create.sql b/pkg/storage/SQL/plan_discussion/create.sql index cfb34a1dd7..c96be14356 100644 --- a/pkg/storage/SQL/plan_discussion/create.sql +++ b/pkg/storage/SQL/plan_discussion/create.sql @@ -4,7 +4,6 @@ INSERT INTO plan_discussion( content, user_role, user_role_description, - status, is_assessment, created_by, modified_by @@ -15,7 +14,6 @@ VALUES ( :content, :user_role, :user_role_description, - :status, :is_assessment, :created_by, :modified_by @@ -25,7 +23,6 @@ model_plan_id, content, user_role, user_role_description, -status, is_assessment, created_by, created_dts, diff --git a/pkg/storage/SQL/plan_discussion/delete.sql b/pkg/storage/SQL/plan_discussion/delete.sql index 83653e394b..e9d3637559 100644 --- a/pkg/storage/SQL/plan_discussion/delete.sql +++ b/pkg/storage/SQL/plan_discussion/delete.sql @@ -5,7 +5,6 @@ model_plan_id, content, user_role, user_role_description, -status, is_assessment, created_by, created_dts, diff --git a/pkg/storage/SQL/plan_discussion/get_by_id.sql b/pkg/storage/SQL/plan_discussion/get_by_id.sql index f4c879469d..b176e1b487 100644 --- a/pkg/storage/SQL/plan_discussion/get_by_id.sql +++ b/pkg/storage/SQL/plan_discussion/get_by_id.sql @@ -4,7 +4,6 @@ SELECT content, user_role, user_role_description, - status, is_assessment, created_by, created_dts, diff --git a/pkg/storage/SQL/plan_discussion/get_by_model_plan_id_LOADER.sql b/pkg/storage/SQL/plan_discussion/get_by_model_plan_id_LOADER.sql index 2cb2956df6..6ee2ee5a88 100644 --- a/pkg/storage/SQL/plan_discussion/get_by_model_plan_id_LOADER.sql +++ b/pkg/storage/SQL/plan_discussion/get_by_model_plan_id_LOADER.sql @@ -12,7 +12,6 @@ SELECT disc.content, disc.user_role, disc.user_role_description, - disc.status, disc.is_assessment, disc.created_by, disc.created_dts, diff --git a/pkg/storage/SQL/plan_discussion/update.sql b/pkg/storage/SQL/plan_discussion/update.sql index 6172e1864d..98ac59b068 100644 --- a/pkg/storage/SQL/plan_discussion/update.sql +++ b/pkg/storage/SQL/plan_discussion/update.sql @@ -4,7 +4,6 @@ SET content = :content, user_role = :user_role, user_role_description = :user_role_description, - status = :status, modified_by = :modified_by, modified_dts = CURRENT_TIMESTAMP WHERE id = :id @@ -14,7 +13,6 @@ model_plan_id, content, user_role, user_role_description, -status, is_assessment, created_by, created_dts, diff --git a/src/components/AskAQuestion/index.test.tsx b/src/components/AskAQuestion/index.test.tsx index 8ff77e59e7..eadbe542ab 100644 --- a/src/components/AskAQuestion/index.test.tsx +++ b/src/components/AskAQuestion/index.test.tsx @@ -21,7 +21,6 @@ const discussionResult = { content: 'This is a question.', createdBy: 'John Doe', createdDts: '2022-05-12T15:01:39.190679Z', - status: 'UNANSWERED', replies: [] }, { @@ -30,12 +29,10 @@ const discussionResult = { content: 'This is a second question.', createdBy: 'Jane Doe', createdDts: '2022-05-12T15:01:39.190679Z', - status: 'ANSWERED', replies: [ { __typename: 'DiscussionReply', discussionID: '456', - resolution: true, id: 'abc', content: 'This is an answer.', createdBy: 'Jack Doe', @@ -103,11 +100,13 @@ describe('Ask a Question Component', () => { expect( getByText( - 'Need help with something? Ask a question here and someone will reply. Questions and answers will display in Discussions. If you need help on a specific question or field, please include the name of the question or field and the section it’s located in.' + 'Need help with something? Start a discussion and you’ll be notified of any replies. If you need help on a specific question or field, please include the name of the question or field and the section it’s located in.' ) ).toBeInTheDocument(); - expect(getByText('Type your question')).toBeInTheDocument(); + expect( + getByText('Type your question or discussion topic') + ).toBeInTheDocument(); }); }); }); diff --git a/src/components/shared/TruncatedText/index.tsx b/src/components/shared/TruncatedText/index.tsx index f2af399eca..76ad2a0a43 100644 --- a/src/components/shared/TruncatedText/index.tsx +++ b/src/components/shared/TruncatedText/index.tsx @@ -1,6 +1,10 @@ import React, { useState } from 'react'; -import { Button } from '@trussworks/react-uswds'; -import classnames from 'classnames'; +import { useTranslation } from 'react-i18next'; +import { + Button, + IconExpandLess, + IconExpandMore +} from '@trussworks/react-uswds'; // This component takes free form text and a character limit and // will return the whole text until it reaches the character limit, once @@ -10,31 +14,29 @@ import classnames from 'classnames'; type TruncatedTextProps = { id: string; - label: string; text: string; charLimit: number; - closeLabel?: string; className?: string; }; const TruncatedText = ({ id, - label, text, charLimit, - closeLabel, className }: TruncatedTextProps) => { + const { t: generalT } = useTranslation('general'); + const [isOpen, setOpen] = useState(true); - const arrowClassNames = classnames( - 'fa', - isOpen ? 'fa-caret-down' : 'fa-caret-right' - ); // If text is shorter then specified character limit, just // return the whole text if (text.length < charLimit) { - return
{text}
; + return ( +
+ {text} +
+ ); } // Text is longer then specified character limit, truncate text @@ -44,17 +46,19 @@ const TruncatedText = ({ return (
- {isOpen ? `${startOfText}... ` : `${text} `} + + {isOpen ? `${startOfText}... ` : `${text} `} +
); diff --git a/src/gql/gen/graphql.ts b/src/gql/gen/graphql.ts index ea0d462447..677fa321e5 100644 --- a/src/gql/gen/graphql.ts +++ b/src/gql/gen/graphql.ts @@ -292,7 +292,6 @@ export type DiscussionReply = { modifiedBy?: Maybe; modifiedByUserAccount?: Maybe; modifiedDts?: Maybe; - resolution?: Maybe; userRole?: Maybe; userRoleDescription?: Maybe; }; @@ -304,7 +303,6 @@ export type DiscussionReply = { */ export type DiscussionReplyChanges = { content?: InputMaybe; - resolution?: InputMaybe; userRole?: InputMaybe; userRoleDescription?: InputMaybe; }; @@ -313,7 +311,6 @@ export type DiscussionReplyChanges = { export type DiscussionReplyCreateInput = { content: Scalars['String']['input']; discussionID: Scalars['UUID']['input']; - resolution?: Scalars['Boolean']['input']; userRole?: InputMaybe; userRoleDescription?: InputMaybe; }; @@ -324,12 +321,6 @@ export type DiscussionRoleSelection = { userRoleDescription?: Maybe; }; -export enum DiscussionStatus { - ANSWERED = 'ANSWERED', - UNANSWERED = 'UNANSWERED', - WAITING_FOR_RESPONSE = 'WAITING_FOR_RESPONSE' -} - export enum DiscussionUserRole { CMS_SYSTEM_SERVICE_TEAM = 'CMS_SYSTEM_SERVICE_TEAM', IT_ARCHITECT = 'IT_ARCHITECT', @@ -1397,7 +1388,6 @@ export type PlanDiscussion = { modifiedByUserAccount?: Maybe; modifiedDts?: Maybe; replies: Array; - status: DiscussionStatus; userRole?: Maybe; userRoleDescription?: Maybe; }; @@ -1409,7 +1399,6 @@ export type PlanDiscussion = { */ export type PlanDiscussionChanges = { content?: InputMaybe; - status?: InputMaybe; userRole?: InputMaybe; userRoleDescription?: InputMaybe; }; diff --git a/src/i18n/en-US/draftModelPlan/discussions.ts b/src/i18n/en-US/draftModelPlan/discussions.ts index ecf722374d..6c3a470bbd 100644 --- a/src/i18n/en-US/draftModelPlan/discussions.ts +++ b/src/i18n/en-US/draftModelPlan/discussions.ts @@ -1,47 +1,62 @@ const discussions = { heading: 'Discussions', modalHeading: 'Model discussions', - askAQuestion: 'Ask a question', + discussionBanner: { + discussion: ' discussion', + discussion_plural: ' discussions' + }, + discussionPanelHeading: 'Start a discussion', + discussionPanelReply: 'Discussion', description: - 'Need help with something? Ask a question here and someone will reply. Questions and answers will display in Discussions. If you need help on a specific question or field, please include the name of the question or field and the section it’s located in.', + 'Need help with something? Start a discussion and you’ll be notified of any replies. If you need help on a specific question or field, please include the name of the question or field and the section it’s located in.', + allFieldsRequired: 'All fields marked with * are required.', noDiscussions: 'There are no discussions yet. ', - askAQuestionLink: 'Ask a question', - toGetStarted: ' to get started', - typeQuestion: 'Type your question', - typeAnswer: 'Type your answer', - save: 'Save question', - saveAnswer: 'Save answer', + askAQuestionLink: 'Start a discussion', + typeQuestion: 'Type your question or discussion topic', + typeQuestionHelpText: + 'To tag a solution team or individual, type "@" and begin typing the name. Then, select the team or individual from the list you wish to notify.', + reply: 'Reply', + replies: '{{count}} reply', + replies_plural: '{{count}} replies', + replies_0: 'No replies', + typeReply: 'Type your reply', + save: 'Save discussion', + saveReply: 'Save reply', + lastReply: 'Last reply {{date}} at {{time}}', useLinkAbove: - 'There are no discussions yet. Ask a question using the link above to get started.', - unanswered: 'unanswered question', + 'There are no new discussion topics. Start a discussion and it will appear here.', + newDiscussionTopics: '{{count}} new discussion topic', + newDiscussionTopics_plural: '{{count}} new discussion topics', + discussionWithCount: '{{count}} discussion', + discussionWithCount_plural: '{{count}} discussions', answered: 'answered question', viewDiscussions: 'View discussions', - success: 'Success! Your question has been added.', - successAnswer: 'Success! Your answer has been added.', + success: 'Success! Your discussion topic has been added.', + successReply: 'Success! Your reply has been added.', errorFetch: 'Sorry we encountered a problem fetching your discusssions. Please try again.', error: - 'Sorry, we encountered a problem adding your question. Please try again.', - errorAnswer: - 'Sorry, we encountered a problem adding your answer. Please try again.', - answer: 'Answer', + 'Sorry, we encountered a problem adding your discussion topic. Please try again.', + errorReply: + 'Sorry, we encountered a problem adding your reply. Please try again.', answerDescription: 'Make sure you know the answer to this question before replying. Once a question has been answered, it cannot be replied to again.', ago: 'ago', justNow: 'Just now', noAnswered: - 'There are no answered questions yet. When a question is answered, it will appear here with the response.', + 'There are no discussions with replies yet. Once a discussion has been replied to, it will appear here.', noUanswered: - 'There are no unanswered questions. Ask a question using the link above.', + 'There are no new discussion topics. Start a discussion and it will appear here.', nonEditor: { noDiscussions: - 'There are no discussions yet. When a question is asked, it will appear here.', + 'There are no discussions with replies yet. When a question is asked, it will appear here.', noQuestions: - 'There are no questions yet. When a question is asked, it will appear here.' + 'There are no new discussion topics. When a question is asked, it will appear here.' }, ariaLabel: 'Discussion Center Modal', assessment: 'MINT Team', viewMoreQuestions: 'View more questions', + viewFewerQuestions: 'View fewer questions', alreadyAnswered: '“{{-question}}” has already been answered. You can view it in the answered questions below.', role: 'Your role', @@ -72,7 +87,9 @@ const discussions = { createdAt: 'Reply created at', userRole: 'User role', userRoleDescription: 'User role description' - } + }, + showReplies: 'Show replies', + hideReplies: 'Hide replies' }; export default discussions; diff --git a/src/i18n/en-US/general.ts b/src/i18n/en-US/general.ts index a3253295f4..b2a5375495 100644 --- a/src/i18n/en-US/general.ts +++ b/src/i18n/en-US/general.ts @@ -21,6 +21,8 @@ const general = { taskListLockBanner: 'Model Plan sections can only be accessed by one person at a time. If you are not actively editing or reviewing this section, please exit out of it so others can access it.', noResults: 'No results', + readMore: 'Read more', + readLess: 'Read less', newTab: 'Open in a new tab' }; diff --git a/src/queries/CreateModelPlanReply.ts b/src/queries/CreateModelPlanReply.ts index b66ed3a352..62e3cf2def 100644 --- a/src/queries/CreateModelPlanReply.ts +++ b/src/queries/CreateModelPlanReply.ts @@ -6,7 +6,6 @@ export default gql` id discussionID content - resolution createdBy createdDts } diff --git a/src/queries/Discussions/CreateModelPlanDiscussion.ts b/src/queries/Discussions/CreateModelPlanDiscussion.ts index c56df6d3bc..3e2e7cbacc 100644 --- a/src/queries/Discussions/CreateModelPlanDiscussion.ts +++ b/src/queries/Discussions/CreateModelPlanDiscussion.ts @@ -5,7 +5,6 @@ export default gql` createPlanDiscussion(input: $input) { id content - status createdBy createdDts } diff --git a/src/queries/Discussions/GetModelPlanDiscussions.ts b/src/queries/Discussions/GetModelPlanDiscussions.ts index c55dca37c3..cc9b15fe98 100644 --- a/src/queries/Discussions/GetModelPlanDiscussions.ts +++ b/src/queries/Discussions/GetModelPlanDiscussions.ts @@ -12,7 +12,6 @@ export default gql` createdDts userRole userRoleDescription - status isAssessment createdByUserAccount { commonName @@ -26,7 +25,6 @@ export default gql` isAssessment createdBy createdDts - resolution createdByUserAccount { commonName } diff --git a/src/queries/Discussions/UpdateModelPlanDiscussion.ts b/src/queries/Discussions/UpdateModelPlanDiscussion.ts index e54329ed8e..c14fd4595d 100644 --- a/src/queries/Discussions/UpdateModelPlanDiscussion.ts +++ b/src/queries/Discussions/UpdateModelPlanDiscussion.ts @@ -7,7 +7,6 @@ export default gql` ) { updatePlanDiscussion(id: $id, changes: $changes) { id - status } } `; diff --git a/src/queries/Discussions/types/CreateModelPlanDiscussion.ts b/src/queries/Discussions/types/CreateModelPlanDiscussion.ts index 6496fe5885..d3265c24f5 100644 --- a/src/queries/Discussions/types/CreateModelPlanDiscussion.ts +++ b/src/queries/Discussions/types/CreateModelPlanDiscussion.ts @@ -3,7 +3,7 @@ // @generated // This file was automatically generated and should not be edited. -import { PlanDiscussionCreateInput, DiscussionStatus } from "./../../../types/graphql-global-types"; +import { PlanDiscussionCreateInput } from "./../../../types/graphql-global-types"; // ==================================================== // GraphQL mutation operation: CreateModelPlanDiscussion @@ -13,7 +13,6 @@ export interface CreateModelPlanDiscussion_createPlanDiscussion { __typename: "PlanDiscussion"; id: UUID; content: string | null; - status: DiscussionStatus; createdBy: UUID; createdDts: Time; } diff --git a/src/queries/Discussions/types/GetModelPlanDiscussions.ts b/src/queries/Discussions/types/GetModelPlanDiscussions.ts index 1f42be40fa..0aa5b76649 100644 --- a/src/queries/Discussions/types/GetModelPlanDiscussions.ts +++ b/src/queries/Discussions/types/GetModelPlanDiscussions.ts @@ -3,7 +3,7 @@ // @generated // This file was automatically generated and should not be edited. -import { DiscussionUserRole, DiscussionStatus } from "./../../../types/graphql-global-types"; +import { DiscussionUserRole } from "./../../../types/graphql-global-types"; // ==================================================== // GraphQL query operation: GetModelPlanDiscussions @@ -29,7 +29,6 @@ export interface GetModelPlanDiscussions_modelPlan_discussions_replies { isAssessment: boolean; createdBy: UUID; createdDts: Time; - resolution: boolean | null; createdByUserAccount: GetModelPlanDiscussions_modelPlan_discussions_replies_createdByUserAccount; } @@ -41,7 +40,6 @@ export interface GetModelPlanDiscussions_modelPlan_discussions { createdDts: Time; userRole: DiscussionUserRole | null; userRoleDescription: string | null; - status: DiscussionStatus; isAssessment: boolean; createdByUserAccount: GetModelPlanDiscussions_modelPlan_discussions_createdByUserAccount; replies: GetModelPlanDiscussions_modelPlan_discussions_replies[]; diff --git a/src/queries/Discussions/types/UpdateModelPlanDiscussion.ts b/src/queries/Discussions/types/UpdateModelPlanDiscussion.ts index 99f5a92674..3c565a4b0c 100644 --- a/src/queries/Discussions/types/UpdateModelPlanDiscussion.ts +++ b/src/queries/Discussions/types/UpdateModelPlanDiscussion.ts @@ -3,7 +3,7 @@ // @generated // This file was automatically generated and should not be edited. -import { PlanDiscussionChanges, DiscussionStatus } from "./../../../types/graphql-global-types"; +import { PlanDiscussionChanges } from "./../../../types/graphql-global-types"; // ==================================================== // GraphQL mutation operation: UpdateModelPlanDiscussion @@ -12,7 +12,6 @@ import { PlanDiscussionChanges, DiscussionStatus } from "./../../../types/graphq export interface UpdateModelPlanDiscussion_updatePlanDiscussion { __typename: "PlanDiscussion"; id: UUID; - status: DiscussionStatus; } export interface UpdateModelPlanDiscussion { diff --git a/src/queries/GetAllModelData.ts b/src/queries/GetAllModelData.ts index 688f741e6d..8fc79806cf 100644 --- a/src/queries/GetAllModelData.ts +++ b/src/queries/GetAllModelData.ts @@ -372,7 +372,6 @@ export default gql` userRole userRoleDescription createdDts - status replies { id discussionID @@ -383,7 +382,6 @@ export default gql` userRole userRoleDescription createdDts - resolution } } } diff --git a/src/queries/GetAllSingleModelPlan.ts b/src/queries/GetAllSingleModelPlan.ts index 2d6621f0ea..3dcb56123a 100644 --- a/src/queries/GetAllSingleModelPlan.ts +++ b/src/queries/GetAllSingleModelPlan.ts @@ -371,7 +371,6 @@ export default gql` userRole userRoleDescription createdDts - status replies { id discussionID @@ -382,7 +381,6 @@ export default gql` userRole userRoleDescription createdDts - resolution } } } diff --git a/src/queries/GetModelPlan.ts b/src/queries/GetModelPlan.ts index ddbf872b5b..117fdb2ec4 100644 --- a/src/queries/GetModelPlan.ts +++ b/src/queries/GetModelPlan.ts @@ -41,14 +41,12 @@ export default gql` content createdBy createdDts - status replies { id discussionID content createdBy createdDts - resolution } } generalCharacteristics { diff --git a/src/queries/GetModelPlans.ts b/src/queries/GetModelPlans.ts index 26933ed2cd..e7c89580e5 100644 --- a/src/queries/GetModelPlans.ts +++ b/src/queries/GetModelPlans.ts @@ -44,10 +44,8 @@ export default gql` } discussions { id - status replies { id - resolution } } crTdls @include(if: $isMAC) { diff --git a/src/queries/types/CreateModelPlanReply.ts b/src/queries/types/CreateModelPlanReply.ts index 61f1fbbbf5..7eebc6d1d5 100644 --- a/src/queries/types/CreateModelPlanReply.ts +++ b/src/queries/types/CreateModelPlanReply.ts @@ -14,7 +14,6 @@ export interface CreateModelPlanReply_createDiscussionReply { id: UUID; discussionID: UUID; content: string | null; - resolution: boolean | null; createdBy: UUID; createdDts: Time; } diff --git a/src/queries/types/GetAllModelData.ts b/src/queries/types/GetAllModelData.ts index cfc8ac2bc8..9b385a7e54 100644 --- a/src/queries/types/GetAllModelData.ts +++ b/src/queries/types/GetAllModelData.ts @@ -3,7 +3,7 @@ // @generated // This file was automatically generated and should not be edited. -import { ModelStatus, ModelCategory, CMSCenter, CMMIGroup, ModelType, TaskStatus, AuthorityAllowance, WaiverType, AlternativePaymentModelType, KeyCharacteristic, GeographyType, GeographyApplication, AgreementType, ParticipantCommunicationType, ParticipantRiskType, ParticipantsIDType, ConfidenceType, RecruitmentType, ParticipantSelectionType, ParticipantsType, FrequencyType, ProviderAddType, ProviderLeaveType, OverlapType, BeneficiariesType, SelectionMethodType, TriStateAnswer, CcmInvolvmentType, DataStartsType, DataFrequencyType, EvaluationApproachType, DataForMonitoringType, DataToSendParticipantsType, DataFullTimeOrIncrementalType, MonitoringFileType, ModelLearningSystemType, AgencyOrStateHelpType, StakeholdersType, ContractorSupportType, BenchmarkForPerformanceType, PayType, ClaimsBasedPayType, ComplexityCalculationLevelType, AnticipatedPaymentFrequencyType, FundingSource, PayRecipient, NonClaimsBasedPayType, TeamRole, DiscussionUserRole, DiscussionStatus } from "./../../types/graphql-global-types"; +import { ModelStatus, ModelCategory, CMSCenter, CMMIGroup, ModelType, TaskStatus, AuthorityAllowance, WaiverType, AlternativePaymentModelType, KeyCharacteristic, GeographyType, GeographyApplication, AgreementType, ParticipantCommunicationType, ParticipantRiskType, ParticipantsIDType, ConfidenceType, RecruitmentType, ParticipantSelectionType, ParticipantsType, FrequencyType, ProviderAddType, ProviderLeaveType, OverlapType, BeneficiariesType, SelectionMethodType, TriStateAnswer, CcmInvolvmentType, DataStartsType, DataFrequencyType, EvaluationApproachType, DataForMonitoringType, DataToSendParticipantsType, DataFullTimeOrIncrementalType, MonitoringFileType, ModelLearningSystemType, AgencyOrStateHelpType, StakeholdersType, ContractorSupportType, BenchmarkForPerformanceType, PayType, ClaimsBasedPayType, ComplexityCalculationLevelType, AnticipatedPaymentFrequencyType, FundingSource, PayRecipient, NonClaimsBasedPayType, TeamRole, DiscussionUserRole } from "./../../types/graphql-global-types"; // ==================================================== // GraphQL query operation: GetAllModelData @@ -417,7 +417,6 @@ export interface GetAllModelData_modelPlanCollection_discussions_replies { userRole: DiscussionUserRole | null; userRoleDescription: string | null; createdDts: Time; - resolution: boolean | null; } export interface GetAllModelData_modelPlanCollection_discussions { @@ -428,7 +427,6 @@ export interface GetAllModelData_modelPlanCollection_discussions { userRole: DiscussionUserRole | null; userRoleDescription: string | null; createdDts: Time; - status: DiscussionStatus; replies: GetAllModelData_modelPlanCollection_discussions_replies[]; } diff --git a/src/queries/types/GetAllSingleModelData.ts b/src/queries/types/GetAllSingleModelData.ts index 45c778c0b8..bddcf571a9 100644 --- a/src/queries/types/GetAllSingleModelData.ts +++ b/src/queries/types/GetAllSingleModelData.ts @@ -3,7 +3,7 @@ // @generated // This file was automatically generated and should not be edited. -import { ModelStatus, ModelCategory, CMSCenter, CMMIGroup, ModelType, TaskStatus, AuthorityAllowance, WaiverType, AlternativePaymentModelType, KeyCharacteristic, GeographyType, GeographyApplication, AgreementType, ParticipantCommunicationType, ParticipantRiskType, ParticipantsIDType, ConfidenceType, RecruitmentType, ParticipantSelectionType, ParticipantsType, FrequencyType, ProviderAddType, ProviderLeaveType, OverlapType, BeneficiariesType, SelectionMethodType, TriStateAnswer, CcmInvolvmentType, DataStartsType, DataFrequencyType, EvaluationApproachType, DataForMonitoringType, DataToSendParticipantsType, DataFullTimeOrIncrementalType, MonitoringFileType, ModelLearningSystemType, AgencyOrStateHelpType, StakeholdersType, ContractorSupportType, BenchmarkForPerformanceType, PayType, ClaimsBasedPayType, ComplexityCalculationLevelType, AnticipatedPaymentFrequencyType, FundingSource, PayRecipient, NonClaimsBasedPayType, TeamRole, DiscussionUserRole, DiscussionStatus } from "./../../types/graphql-global-types"; +import { ModelStatus, ModelCategory, CMSCenter, CMMIGroup, ModelType, TaskStatus, AuthorityAllowance, WaiverType, AlternativePaymentModelType, KeyCharacteristic, GeographyType, GeographyApplication, AgreementType, ParticipantCommunicationType, ParticipantRiskType, ParticipantsIDType, ConfidenceType, RecruitmentType, ParticipantSelectionType, ParticipantsType, FrequencyType, ProviderAddType, ProviderLeaveType, OverlapType, BeneficiariesType, SelectionMethodType, TriStateAnswer, CcmInvolvmentType, DataStartsType, DataFrequencyType, EvaluationApproachType, DataForMonitoringType, DataToSendParticipantsType, DataFullTimeOrIncrementalType, MonitoringFileType, ModelLearningSystemType, AgencyOrStateHelpType, StakeholdersType, ContractorSupportType, BenchmarkForPerformanceType, PayType, ClaimsBasedPayType, ComplexityCalculationLevelType, AnticipatedPaymentFrequencyType, FundingSource, PayRecipient, NonClaimsBasedPayType, TeamRole, DiscussionUserRole } from "./../../types/graphql-global-types"; // ==================================================== // GraphQL query operation: GetAllSingleModelData @@ -417,7 +417,6 @@ export interface GetAllSingleModelData_modelPlan_discussions_replies { userRole: DiscussionUserRole | null; userRoleDescription: string | null; createdDts: Time; - resolution: boolean | null; } export interface GetAllSingleModelData_modelPlan_discussions { @@ -428,7 +427,6 @@ export interface GetAllSingleModelData_modelPlan_discussions { userRole: DiscussionUserRole | null; userRoleDescription: string | null; createdDts: Time; - status: DiscussionStatus; replies: GetAllSingleModelData_modelPlan_discussions_replies[]; } diff --git a/src/queries/types/GetModelPlan.ts b/src/queries/types/GetModelPlan.ts index 8ef8ec02e0..addb11c33d 100644 --- a/src/queries/types/GetModelPlan.ts +++ b/src/queries/types/GetModelPlan.ts @@ -3,7 +3,7 @@ // @generated // This file was automatically generated and should not be edited. -import { ModelStatus, TaskStatus, TeamRole, DiscussionStatus, PrepareForClearanceStatus } from "./../../types/graphql-global-types"; +import { ModelStatus, TaskStatus, TeamRole, PrepareForClearanceStatus } from "./../../types/graphql-global-types"; // ==================================================== // GraphQL query operation: GetModelPlan @@ -55,7 +55,6 @@ export interface GetModelPlan_modelPlan_discussions_replies { content: string | null; createdBy: UUID; createdDts: Time; - resolution: boolean | null; } export interface GetModelPlan_modelPlan_discussions { @@ -64,7 +63,6 @@ export interface GetModelPlan_modelPlan_discussions { content: string | null; createdBy: UUID; createdDts: Time; - status: DiscussionStatus; replies: GetModelPlan_modelPlan_discussions_replies[]; } diff --git a/src/queries/types/GetModelPlans.ts b/src/queries/types/GetModelPlans.ts index fc64f5a0a4..8bb8b3c356 100644 --- a/src/queries/types/GetModelPlans.ts +++ b/src/queries/types/GetModelPlans.ts @@ -3,7 +3,7 @@ // @generated // This file was automatically generated and should not be edited. -import { ModelPlanFilter, ModelStatus, ModelCategory, KeyCharacteristic, TeamRole, DiscussionStatus } from "./../../types/graphql-global-types"; +import { ModelPlanFilter, ModelStatus, ModelCategory, KeyCharacteristic, TeamRole } from "./../../types/graphql-global-types"; // ==================================================== // GraphQL query operation: GetModelPlans @@ -52,13 +52,11 @@ export interface GetModelPlans_modelPlanCollection_collaborators { export interface GetModelPlans_modelPlanCollection_discussions_replies { __typename: "DiscussionReply"; id: UUID; - resolution: boolean | null; } export interface GetModelPlans_modelPlanCollection_discussions { __typename: "PlanDiscussion"; id: UUID; - status: DiscussionStatus; replies: GetModelPlans_modelPlanCollection_discussions_replies[]; } diff --git a/src/types/graphql-global-types.ts b/src/types/graphql-global-types.ts index 6fc72e936a..bf0f661fc6 100644 --- a/src/types/graphql-global-types.ts +++ b/src/types/graphql-global-types.ts @@ -181,12 +181,6 @@ export enum DataToSendParticipantsType { PROVIDER_LEVEL_DATA = "PROVIDER_LEVEL_DATA", } -export enum DiscussionStatus { - ANSWERED = "ANSWERED", - UNANSWERED = "UNANSWERED", - WAITING_FOR_RESPONSE = "WAITING_FOR_RESPONSE", -} - export enum DiscussionUserRole { CMS_SYSTEM_SERVICE_TEAM = "CMS_SYSTEM_SERVICE_TEAM", IT_ARCHITECT = "IT_ARCHITECT", @@ -604,7 +598,6 @@ export interface DiscussionReplyCreateInput { content: string; userRole?: DiscussionUserRole | null; userRoleDescription?: string | null; - resolution: boolean; } /** @@ -720,7 +713,6 @@ export interface PlanCrTdlCreateInput { */ export interface PlanDiscussionChanges { content?: string | null; - status?: DiscussionStatus | null; userRole?: DiscussionUserRole | null; userRoleDescription?: string | null; } diff --git a/src/utils/date.ts b/src/utils/date.ts index d3e3ff9382..fa9844ed7c 100644 --- a/src/utils/date.ts +++ b/src/utils/date.ts @@ -48,6 +48,46 @@ export const getTimeElapsed = (discussionCreated: string) => { return dateString; }; +export const getDaysElapsed = (discussionCreated: string) => { + const now = DateTime.local(); + const creationTime = DateTime.fromISO(discussionCreated); + + const timePassed = now + .diff(creationTime, ['years', 'months', 'days']) + .toObject(); + + let dateString = ''; + + Object.keys(timePassed).forEach(time => { + if (Math.abs(timePassed[time as keyof typeof getTimeElapsed]) >= 1) { + const floatTime = Math.round( + Math.abs(timePassed[time as keyof typeof getTimeElapsed]) + ); + + // Only show parent most level of time, rather than all increments + if (dateString === '') { + dateString += `${floatTime} ${ + timePassed[time as keyof typeof getTimeElapsed] !== 1 + ? time + : time.slice(0, -1) // If singular, remove last letter 's's from time string + } ago`; + } + } + if (time === 'days') { + if ( + Math.abs(timePassed[time as keyof typeof getTimeElapsed]) > 0 && + Math.abs(timePassed[time as keyof typeof getTimeElapsed]) < 1 + ) { + if (dateString === '') { + dateString = 'today'; + } + } + } + }); + + return dateString; +}; + export const isDateInPast = (date: string | null): boolean => { if (date && new Date() > new Date(date)) { return true; diff --git a/src/utils/modelPlan.ts b/src/utils/modelPlan.ts index c45e7fd359..b7c8b72860 100644 --- a/src/utils/modelPlan.ts +++ b/src/utils/modelPlan.ts @@ -95,19 +95,6 @@ export const translateDocumentType = (documentType: DocumentType) => { } }; -// Returns an object with th number of discussions with answered and unanswered questions -export const getUnansweredQuestions = (discussions: DiscussionType[]) => { - const unansweredQuestions = - discussions?.filter( - (discussion: DiscussionType) => discussion.status === 'UNANSWERED' - ).length || 0; - const answeredQuestions = discussions?.length - unansweredQuestions; - return { - unansweredQuestions, - answeredQuestions - }; -}; - // Sorts discussions by the most recent reply export const sortRepliesByDate = ( discussionA: DiscussionType, diff --git a/src/views/Landing/index.tsx b/src/views/Landing/index.tsx index c6369e022d..14dd12e036 100644 --- a/src/views/Landing/index.tsx +++ b/src/views/Landing/index.tsx @@ -16,10 +16,7 @@ import UswdsReactLink from 'components/LinkWrapper'; import NDABanner from 'components/NDABanner'; import useCheckResponsiveScreen from 'hooks/useCheckMobile'; import { GetModelPlanDiscussions_modelPlan_discussions as DiscussionType } from 'queries/Discussions/types/GetModelPlanDiscussions'; -import { - DiscussionStatus, - DiscussionUserRole -} from 'types/graphql-global-types'; +import { DiscussionUserRole } from 'types/graphql-global-types'; import FormatDiscussion from 'views/ModelPlan/Discussions/FormatDiscussion'; import './index.scss'; @@ -327,7 +324,6 @@ const DiscussionCard = () => { userRole: DiscussionUserRole.MODEL_TEAM, userRoleDescription: '', createdDts: hour3ago, - status: DiscussionStatus.ANSWERED, isAssessment: false, createdByUserAccount: { commonName: 'Jane Middleton', @@ -344,7 +340,6 @@ const DiscussionCard = () => { userRoleDescription: '', createdBy: 'd508dcaa-a455-4848-b717-49cbe5e3cf6b', createdDts: justNow, - resolution: true, createdByUserAccount: { commonName: 'Zoe Hruban', __typename: 'UserAccount' @@ -360,9 +355,6 @@ const DiscussionCard = () => {
null} setDiscussionType={() => null} setReply={() => null} /> diff --git a/src/views/ModelPlan/Discussions/FormatDiscussion.tsx b/src/views/ModelPlan/Discussions/FormatDiscussion.tsx index d0dfde3942..a7d7bb1f1a 100644 --- a/src/views/ModelPlan/Discussions/FormatDiscussion.tsx +++ b/src/views/ModelPlan/Discussions/FormatDiscussion.tsx @@ -9,16 +9,11 @@ import { GetModelPlanDiscussions_modelPlan_discussions as DiscussionType, GetModelPlanDiscussions_modelPlan_discussions_replies as ReplyType } from 'queries/Discussions/types/GetModelPlanDiscussions'; -import { DiscussionStatus } from 'types/graphql-global-types'; -import { sortRepliesByDate } from 'utils/modelPlan'; import SingleDiscussion from './SingleDiscussion'; type FormatDiscussionProps = { discussionsContent: DiscussionType[]; - status: DiscussionStatus; - hasEditAccess?: boolean; - setDiscussionStatusMessage: (a: string) => void; setDiscussionType: (a: 'question' | 'reply' | 'discussion') => void; setReply: (discussion: DiscussionType | ReplyType) => void; setIsDiscussionOpen?: (value: boolean) => void; @@ -26,9 +21,6 @@ type FormatDiscussionProps = { const FormatDiscussion = ({ discussionsContent, - status, - hasEditAccess, - setDiscussionStatusMessage, setDiscussionType, setReply, setIsDiscussionOpen @@ -36,10 +28,6 @@ const FormatDiscussion = ({ const { t } = useTranslation('discussions'); const [isAccordionExpanded, setIsAccordionExpanded] = useState(false); - if (status === 'ANSWERED') { - discussionsContent.sort(sortRepliesByDate); // Sort discusssions by the most recent reply for answered questions - } - const discussionsContentList = isAccordionExpanded ? discussionsContent : discussionsContent.slice(0, 5); @@ -55,45 +43,18 @@ const FormatDiscussion = ({ 'margin-top-2': index === 0 })} > - {discussion.replies.length > 0 ? ( - // If discussions has replies, join together in array for rendering as a connected discussion - <> - {[discussion, ...discussion.replies].map( - (discussionReply: ReplyType | DiscussionType, replyIndex) => ( - - ) - )} - - ) : ( - // Render only question if no replies - - )} + {/* Divider to separate questions if not the last question */} - {index !== discussionsContentList.length - 1 && ( - - )} + {index !== discussionsContentList.length - 1 && } {!isAccordionExpanded && discussionsContent.length > 5 && index === discussionsContentList.length - 1 && ( diff --git a/src/views/ModelPlan/Discussions/QuestionAndReply.tsx b/src/views/ModelPlan/Discussions/QuestionAndReply.tsx index 44f8c5c5a8..befbaab3d9 100644 --- a/src/views/ModelPlan/Discussions/QuestionAndReply.tsx +++ b/src/views/ModelPlan/Discussions/QuestionAndReply.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; import { useHistory } from 'react-router-dom'; import { useQuery } from '@apollo/client'; import { @@ -14,11 +14,9 @@ import { Field, Form, Formik, FormikProps } from 'formik'; import * as Yup from 'yup'; import PageHeading from 'components/PageHeading'; -import AssessmentIcon from 'components/shared/AssessmentIcon'; import { ErrorAlert, ErrorAlertMessage } from 'components/shared/ErrorAlert'; import FieldErrorMsg from 'components/shared/FieldErrorMsg'; import FieldGroup from 'components/shared/FieldGroup'; -import IconInitial from 'components/shared/IconInitial'; import RequiredAsterisk from 'components/shared/RequiredAsterisk'; import GetMostRecentRoleSelection from 'queries/Discussions/GetMostRecentRoleSelection'; import { @@ -27,21 +25,21 @@ import { } from 'queries/Discussions/types/GetModelPlanDiscussions'; import { GetMostRecentRoleSelection as GetMostRecentRoleSelectionType } from 'queries/Discussions/types/GetMostRecentRoleSelection'; import { DiscussionUserRole } from 'types/graphql-global-types'; -import { getTimeElapsed } from 'utils/date'; import flattenErrors from 'utils/flattenErrors'; import { sortOtherEnum } from 'utils/modelPlan'; -import { DicussionFormPropTypes } from '.'; +import DiscussionUserInfo from './_components/DiscussionUserInfo'; +import Replies from './Replies'; +import { DiscussionFormPropTypes } from '.'; type QuestionAndReplyProps = { closeModal?: () => void; discussionReplyID?: string | null | undefined; - handleCreateDiscussion: (formikValues: DicussionFormPropTypes) => void; + handleCreateDiscussion: (formikValues: DiscussionFormPropTypes) => void; queryParams?: URLSearchParams; renderType: 'question' | 'reply'; reply?: DiscussionType | ReplyType | null; setDiscussionReplyID?: (value: string | null | undefined) => void; - setDiscussionStatusMessage?: (value: string) => void; setDiscussionType?: (value: 'question' | 'reply' | 'discussion') => void; setInitQuestion?: (value: boolean) => void; }; @@ -54,7 +52,6 @@ const QuestionAndReply = ({ renderType, reply, setDiscussionReplyID, - setDiscussionStatusMessage, setDiscussionType, setInitQuestion }: QuestionAndReplyProps) => { @@ -77,51 +74,54 @@ const QuestionAndReply = ({ return ( <> - - {renderType === 'question' ? t('askAQuestion') : t('answer')} + + {renderType === 'question' + ? t('discussionPanelHeading') + : t('discussionPanelReply')} -

- {renderType === 'question' ? t('description') : t('answerDescription')} -

+ {renderType === 'question' && ( + <> +

{t('description')}

+

+ + }} + /> +

+ + )} {/* If renderType is reply, render the related question that is being answered */} {renderType === 'reply' && reply && ( -
-
- {reply.isAssessment ? ( -
- {' '} - - {t('assessment')} | {reply.createdByUserAccount.commonName} - -
- ) : ( - - )} - - {getTimeElapsed(reply.createdDts) - ? getTimeElapsed(reply.createdDts) + t('ago') - : t('justNow')} - + <> +
+ + +
+

{reply.content}

+
- {reply.userRole && ( -

- {reply.userRole === DiscussionUserRole.NONE_OF_THE_ABOVE - ? reply.userRoleDescription - : t(`userRole.${reply.userRole}`)} -

- )} + -
-

{reply.content}

-
-
+ + {t('reply')} + +

+ + }} + /> +

+ )} - {(formikProps: FormikProps) => { + {(formikProps: FormikProps) => { const { errors, values, @@ -177,7 +177,7 @@ const QuestionAndReply = ({
diff --git a/src/views/ModelPlan/Discussions/Replies/index.tsx b/src/views/ModelPlan/Discussions/Replies/index.tsx new file mode 100644 index 0000000000..53e46850e0 --- /dev/null +++ b/src/views/ModelPlan/Discussions/Replies/index.tsx @@ -0,0 +1,115 @@ +import React, { Fragment, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Button, + IconExpandLess, + IconExpandMore +} from '@trussworks/react-uswds'; + +import SectionWrapper from 'components/shared/SectionWrapper'; +import TruncatedText from 'components/shared/TruncatedText'; +import { GetModelPlanDiscussions_modelPlan_discussions as DiscussionType } from 'queries/Discussions/types/GetModelPlanDiscussions'; + +import DiscussionUserInfo from '../_components/DiscussionUserInfo'; + +const Replies = ({ + originalDiscussion: { replies } +}: { + originalDiscussion: DiscussionType; +}) => { + const { t: discussionsT } = useTranslation('discussions'); + + const [areRepliesShowing, setAreRepliesShowing] = useState(true); + const [isAccordionExpanded, setIsAccordionExpanded] = useState(false); + + const hasReplies = replies.length > 0; + const repliesList = isAccordionExpanded ? replies : replies.slice(0, 4); + + return ( +
+
+

+ {hasReplies ? ( + <> + {discussionsT('replies', { + count: replies.length + })} + + ) : ( + <> + {/* https://github.com/i18next/i18next/issues/1220#issuecomment-654161038 */} + {discussionsT('replies', { count: 0, context: '0' })} + + )} +

+ {hasReplies && ( + + )} +
+ {hasReplies && areRepliesShowing && ( +
+
+ {repliesList.map((reply, index) => { + return ( + + +
+ +
+
+ ); + })} +
+ {replies.length > 4 && ( + + + + )} +
+ )} +
+ ); +}; + +export default Replies; diff --git a/src/views/ModelPlan/Discussions/SingleDiscussion.tsx b/src/views/ModelPlan/Discussions/SingleDiscussion.tsx index 70aa677507..6afb49890a 100644 --- a/src/views/ModelPlan/Discussions/SingleDiscussion.tsx +++ b/src/views/ModelPlan/Discussions/SingleDiscussion.tsx @@ -2,27 +2,26 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { Button, IconAnnouncement } from '@trussworks/react-uswds'; import classNames from 'classnames'; +import { DateTime } from 'luxon'; -import AssessmentIcon from 'components/shared/AssessmentIcon'; -import IconInitial from 'components/shared/IconInitial'; import { GetModelPlanDiscussions_modelPlan_discussions as DiscussionType, GetModelPlanDiscussions_modelPlan_discussions_replies as ReplyType } from 'queries/Discussions/types/GetModelPlanDiscussions'; -import { DiscussionUserRole } from 'types/graphql-global-types'; -import { getTimeElapsed } from 'utils/date'; +import { getDaysElapsed } from 'utils/date'; + +import DiscussionUserInfo from './_components/DiscussionUserInfo'; type SingleDiscussionProps = { discussion: DiscussionType | ReplyType; index: number; connected?: boolean; answerQuestion?: boolean; - hasEditAccess?: boolean; - setDiscussionStatusMessage: (a: string) => void; setDiscussionType: (a: 'question' | 'reply' | 'discussion') => void; setReply: (discussion: DiscussionType | ReplyType) => void; setIsDiscussionOpen?: (value: boolean) => void; isLast: boolean; + replies: ReplyType[]; }; const SingleDiscussion = ({ @@ -30,91 +29,72 @@ const SingleDiscussion = ({ index, connected, answerQuestion, - hasEditAccess, - setDiscussionStatusMessage, setDiscussionType, setReply, setIsDiscussionOpen, - isLast + isLast, + replies }: SingleDiscussionProps) => { - const { t } = useTranslation('discussions'); - - return ( -
-
- {discussion.isAssessment ? ( -
- {' '} - - {t('assessment')} | {discussion.createdByUserAccount.commonName} - -
- ) : ( - - )} + const { t: discussionT } = useTranslation('discussions'); - - {getTimeElapsed(discussion.createdDts) - ? getTimeElapsed(discussion.createdDts) + t('ago') - : t('justNow')} - -
+ const latestDate = [...replies].reduce( + (pre: any, cur: any) => (Date.parse(pre) > Date.parse(cur) ? pre : cur), + 0 + ); + const timeLastUpdated = DateTime.fromISO( + latestDate.createdDts + ).toLocaleString(DateTime.TIME_SIMPLE); + const daysLastUpdated = getDaysElapsed(latestDate.createdDts); - {discussion.userRole && ( -

- {discussion.userRole === DiscussionUserRole.NONE_OF_THE_ABOVE - ? discussion.userRoleDescription - : t(`userRole.${discussion.userRole}`)} -

- )} + return ( +
+

{discussion.content}

- {/* Rendered a link to answer a question if there are no replies/answers only for Collaborator and Assessment Users */} - {hasEditAccess && answerQuestion && ( -
- - -
- )} +
+ + + {replies.length > 0 && ( +

+ {discussionT('lastReply', { + date: daysLastUpdated, + time: timeLastUpdated + })} +

+ )} +
); diff --git a/src/views/ModelPlan/Discussions/_components/DiscussionUserInfo/index.scss b/src/views/ModelPlan/Discussions/_components/DiscussionUserInfo/index.scss new file mode 100644 index 0000000000..ebc6f949a7 --- /dev/null +++ b/src/views/ModelPlan/Discussions/_components/DiscussionUserInfo/index.scss @@ -0,0 +1,13 @@ +@use 'uswds-core' as *; + +.discussion-user-info { + .dui__userRole { + margin: 0 0 0 2.5rem; + + &--connected { + border-left: .25rem solid color('base-lightest'); + margin-left: .9rem; + padding-left: 1.4rem; + } + } +} diff --git a/src/views/ModelPlan/Discussions/_components/DiscussionUserInfo/index.tsx b/src/views/ModelPlan/Discussions/_components/DiscussionUserInfo/index.tsx new file mode 100644 index 0000000000..85508b888c --- /dev/null +++ b/src/views/ModelPlan/Discussions/_components/DiscussionUserInfo/index.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import AssessmentIcon from 'components/shared/AssessmentIcon'; +import IconInitial from 'components/shared/IconInitial'; +import { + GetModelPlanDiscussions_modelPlan_discussions as DiscussionType, + GetModelPlanDiscussions_modelPlan_discussions_replies as ReplyType +} from 'queries/Discussions/types/GetModelPlanDiscussions'; +import { DiscussionUserRole } from 'types/graphql-global-types'; +import { getTimeElapsed } from 'utils/date'; + +import './index.scss'; + +type DiscussionUserInfoProps = { + connected?: boolean; + discussionTopic: DiscussionType | ReplyType; + index?: number; +}; + +const DiscussionUserInfo = ({ + connected, + discussionTopic, + index +}: DiscussionUserInfoProps) => { + const { t: discussionT } = useTranslation('discussions'); + return ( +
+
+ {discussionTopic.isAssessment ? ( +
+ {' '} + + {discussionT('assessment')} |{' '} + {discussionTopic.createdByUserAccount.commonName} + +
+ ) : ( + + )} + {discussionTopic.userRole && ( +
+

+ {discussionTopic.userRole === DiscussionUserRole.NONE_OF_THE_ABOVE + ? discussionTopic.userRoleDescription + : discussionT(`userRole.${discussionTopic.userRole}`)} +

+
+ )} +
+ + {getTimeElapsed(discussionTopic.createdDts) + ? getTimeElapsed(discussionTopic.createdDts) + discussionT('ago') + : discussionT('justNow')} + +
+ ); +}; + +export default DiscussionUserInfo; diff --git a/src/views/ModelPlan/Discussions/index.scss b/src/views/ModelPlan/Discussions/index.scss index 0053df7c63..e602942bfd 100644 --- a/src/views/ModelPlan/Discussions/index.scss +++ b/src/views/ModelPlan/Discussions/index.scss @@ -11,7 +11,7 @@ background-color: rgba(0, 0, 0, 0.6); z-index: 400; } - + &__content { position: absolute; width: 50%; @@ -21,7 +21,7 @@ right: 0; z-index: 1; line-height: 1.6em; - + @media screen and (max-width: $desktop) { width: 100%; } @@ -32,17 +32,17 @@ box-shadow: 0px .25rem .5rem rgba(0, 0, 0, 0.1); padding: 1rem 2rem 1rem 1rem; } - + &__x-button { background: none; border: 0; line-height: 0; - + &:hover { cursor: pointer; } } - + &__body { padding: 4rem 2rem 2rem; } @@ -69,6 +69,11 @@ background-size: 0rem !important; } - .usa-accordion__content { - padding: 0.75rem 1.25rem calc(1rem - 0.25rem) 1.25rem !important; +.discussion-accordion > .usa-accordion__content { + padding: 0rem 1rem; + + &:empty { + padding-top: 0; + padding-bottom: 0; } +} diff --git a/src/views/ModelPlan/Discussions/index.test.tsx b/src/views/ModelPlan/Discussions/index.test.tsx index bc1313014e..7e4e4e4414 100644 --- a/src/views/ModelPlan/Discussions/index.test.tsx +++ b/src/views/ModelPlan/Discussions/index.test.tsx @@ -11,10 +11,7 @@ import GetModelPlanDiscussions from 'queries/Discussions/GetModelPlanDiscussions import GetMostRecentRoleSelection from 'queries/Discussions/GetMostRecentRoleSelection'; import { GetModelPlanDiscussions as GetModelPlanDiscussionsType } from 'queries/Discussions/types/GetModelPlanDiscussions'; import { GetMostRecentRoleSelection as GetMostRecentRoleSelectionType } from 'queries/Discussions/types/GetMostRecentRoleSelection'; -import { - DiscussionStatus, - DiscussionUserRole -} from 'types/graphql-global-types'; +import { DiscussionUserRole } from 'types/graphql-global-types'; import Discussions from './index'; @@ -30,7 +27,6 @@ const discussionResult: GetModelPlanDiscussionsType = { content: 'This is a question.', createdBy: 'TIDA', createdDts: '2022-05-12T15:01:39.190679Z', - status: DiscussionStatus.UNANSWERED, userRole: DiscussionUserRole.CMS_SYSTEM_SERVICE_TEAM, userRoleDescription: '', isAssessment: false, @@ -46,7 +42,6 @@ const discussionResult: GetModelPlanDiscussionsType = { content: 'This is a second question.', createdBy: 'JFCS', createdDts: '2022-05-12T15:01:39.190679Z', - status: DiscussionStatus.ANSWERED, userRole: DiscussionUserRole.NONE_OF_THE_ABOVE, userRoleDescription: 'Designer', isAssessment: false, @@ -58,7 +53,6 @@ const discussionResult: GetModelPlanDiscussionsType = { { __typename: 'DiscussionReply', discussionID: '456', - resolution: true, id: 'abc', content: 'This is an answer.', userRole: DiscussionUserRole.LEADERSHIP, @@ -142,12 +136,12 @@ describe('Discussion Component', () => { await waitFor(() => { expect(getByText(/This is a question./i)).toBeInTheDocument(); - expect(getByText(/1 unanswered question/i)).toBeInTheDocument(); + expect(getByText(/new discussion topic/i)).toBeInTheDocument(); expect(getByText(/John Doe/i)).toBeInTheDocument(); - expect(getByText(/1 answered question/i)).toBeInTheDocument(); + expect(getByText(/1 discussion/i)).toBeInTheDocument(); expect(getByText(/Jane Doe/i)).toBeInTheDocument(); expect(getByText(/This is a second question./i)).toBeInTheDocument(); - expect(getByText(/Leadership/i)).toBeInTheDocument(); + expect(getByText(/Designer/i)).toBeInTheDocument(); }); }); @@ -169,11 +163,11 @@ describe('Discussion Component', () => { ); await waitFor(async () => { - screen.getByRole('button', { name: /Answer/ }).click(); + screen.getByRole('button', { name: /Reply/ }).click(); expect( getByText( - /Make sure you know the answer to this question before replying. Once a question has been answered, it cannot be replied to again./i + /To tag a solution team or individual, type "@" and begin typing the name. Then, select the team or individual from the list you wish to notify./i ) ).toBeInTheDocument(); @@ -189,7 +183,7 @@ describe('Discussion Component', () => { expect(roleSelect).toHaveValue(DiscussionUserRole.MINT_TEAM); const feedbackField = screen.getByRole('textbox', { - name: /Type your answer/i + name: /Type your reply/i }); userEvent.type(feedbackField, 'Test feedback'); diff --git a/src/views/ModelPlan/Discussions/index.tsx b/src/views/ModelPlan/Discussions/index.tsx index 8486b35ff4..ce9e3a0feb 100644 --- a/src/views/ModelPlan/Discussions/index.tsx +++ b/src/views/ModelPlan/Discussions/index.tsx @@ -26,15 +26,11 @@ import { GetModelPlanDiscussions_modelPlan_discussions_replies as ReplyType, GetModelPlanDiscussionsVariables } from 'queries/Discussions/types/GetModelPlanDiscussions'; -import { UpdateModelPlanDiscussion as UpdateModelPlanDiscussionType } from 'queries/Discussions/types/UpdateModelPlanDiscussion'; -import UpdateModelPlanDiscussion from 'queries/Discussions/UpdateModelPlanDiscussion'; import { CreateModelPlanReply as CreateModelPlanReplyType } from 'queries/types/CreateModelPlanReply'; import { - DiscussionStatus, DiscussionUserRole, PlanDiscussionCreateInput } from 'types/graphql-global-types'; -import { getUnansweredQuestions } from 'utils/modelPlan'; import { isAssessment, isMAC } from 'utils/user'; import DiscussionModalWrapper from './DiscussionModalWrapper'; @@ -50,7 +46,7 @@ export type DiscussionsProps = { askAQuestion?: boolean; }; -export type DicussionFormPropTypes = Omit< +export type DiscussionFormPropTypes = Omit< PlanDiscussionCreateInput, 'modelPlanID' >; @@ -99,10 +95,6 @@ const Discussions = ({ CreateModelPlanReply ); - const [updateDiscussion] = useMutation( - UpdateModelPlanDiscussion - ); - const createDiscussionMethods = { question: createQuestion, reply: createReply @@ -117,10 +109,9 @@ const Discussions = ({ 'question' | 'reply' | 'discussion' >(discussionReplyID ? 'reply' : 'question'); - const [discussionStatus, setDiscussionStatus] = useState<'success' | 'error'>( - 'success' - ); - + const [discussionStatus, setDiscussionStatus] = useState< + 'success' | 'warning' | 'error' | 'info' + >('info'); const [discussionStatusMessage, setDiscussionStatusMessage] = useState(''); // State used to control when the component is being rendered from a form page rather than the task-list @@ -128,11 +119,6 @@ const Discussions = ({ discussionReplyID ? true : askAQuestion ); - const [questionCount, setQuestionCount] = useState({ - answeredQuestions: 0, - unansweredQuestions: 0 - }); - // State and setter used for containing the related question when replying const [reply, setReply] = useState(null); @@ -155,7 +141,6 @@ const Discussions = ({ search: queryParams.toString() }); setInitQuestion(false); - setDiscussionStatus('error'); setDiscussionStatusMessage( t('alreadyAnswered', { question: discussionToReply.content @@ -174,17 +159,9 @@ const Discussions = ({ } else { setDiscussionType('discussion'); } - setQuestionCount(getUnansweredQuestions(discussions)); }, [discussions, initQuestion, readOnly, discussionReplyID]); - // Handles the default expanded render of accordions based on if there are more than zero questions - const openStatus = (status: DiscussionStatus) => { - return status === 'ANSWERED' - ? questionCount.answeredQuestions > 0 - : questionCount.unansweredQuestions > 0; - }; - - const handleCreateDiscussion = (formikValues: DicussionFormPropTypes) => { + const handleCreateDiscussion = (formikValues: DiscussionFormPropTypes) => { let payload: any = {}; // Setting the mutation payload depending on discussionType @@ -196,8 +173,7 @@ const Discussions = ({ } else if (discussionType === 'reply' && reply) { payload = { discussionID: reply.id, - ...formikValues, - resolution: true + ...formikValues }; } else { return; // Currently we have no mutations when discussions is displayed @@ -219,7 +195,10 @@ const Discussions = ({ history.replace({ search: queryParams.toString() }); - handleUpdateDiscussion(reply.id); + refetch().then(() => { + setInitQuestion(false); + setDiscussionType('discussion'); + }); } else { refetch().then(() => { setInitQuestion(false); @@ -232,115 +211,98 @@ const Discussions = ({ } setDiscussionStatus('success'); setDiscussionStatusMessage( - discussionType === 'question' ? t('success') : t('successAnswer') + discussionType === 'question' ? t('success') : t('successReply') ); } }) .catch(() => { setDiscussionStatus('error'); setDiscussionStatusMessage( - discussionType === 'question' ? t('error') : t('errorAnswer') + discussionType === 'question' ? t('error') : t('errorReply') ); }); }; - const handleUpdateDiscussion = (id: string) => { - updateDiscussion({ - variables: { - id, - changes: { - status: 'ANSWERED' // For now any question that has a reply will bw considered "ANSWERED" - } - } - }) - .then(response => { - if (!response?.errors) { - refetch().then(() => { - setInitQuestion(false); - setDiscussionType('discussion'); - }); - } - }) - .catch(() => { - setDiscussionStatus('error'); - setDiscussionStatusMessage(t('error')); - }); - }; - - // Two main discussion accordion types - "Unanswered" and "Answered" based on enum - DiscussionStatus - const discussionAccordion = (Object.keys(DiscussionStatus) as Array< - keyof typeof DiscussionStatus - >) - .filter(status => status !== 'WAITING_FOR_RESPONSE') // Not currently using this status, but it exists for future possibility - .reverse() // Unanswered questions should appear for answered. This method of sorting may need to change if more status/accordions are introduced - .map(status => { - return ( -
- - {questionCount.unansweredQuestions} {t('unanswered')} - {questionCount.unansweredQuestions !== 1 && 's'} - - ) : ( - - {questionCount.answeredQuestions} {t('answered')} - {questionCount.answeredQuestions !== 1 && 's'} - - ), - content: ( - discussion.status === status - )} - status={DiscussionStatus[status]} - hasEditAccess={hasEditAccess} - setDiscussionStatusMessage={setDiscussionStatusMessage} - setDiscussionType={setDiscussionType} - setReply={setReply} - setIsDiscussionOpen={setIsDiscussionOpen} - /> - ), - expanded: true, - id: status, - headingLevel: 'h4' - } - ]} - /> - {/* Sets an infobox beneath each accordion if there are zero questions of that type */} - {!openStatus(DiscussionStatus[status]) && ( - - {hasEditAccess && - (status === 'ANSWERED' ? t('noAnswered') : t('noUanswered'))} - {!hasEditAccess && - (status === 'ANSWERED' - ? t('noAnswered') - : t('nonEditor.noQuestions'))} - + const DiscussionAccordion = ({ + discussionContent, + hasReplies + }: { + discussionContent: DiscussionType[]; + hasReplies?: boolean; + }) => { + return ( + <> + - ); - }); + bordered={false} + multiselectable + items={[ + { + title: ( + + {hasReplies + ? t('discussionWithCount', { + count: discussionContent.length + }) + : t('newDiscussionTopics', { + count: discussionContent.length + })} + + ), + content: ( + + ), + expanded: true, + id: 'discussion-accordion--hasNoReplies', + headingLevel: 'h4' + } + ]} + /> + {discussionContent.length === 0 && ( + + {hasReplies ? t('noAnswered') : t('noUanswered')} + + )} + + ); + }; const renderDiscussionContent = () => { - if (discussions?.length === 0) { + if (discussions.length === 0) { return ( {hasEditAccess ? t('useLinkAbove') : t('nonEditor.noDiscussions')} ); } - return discussionAccordion; + + const discussionsWithNoReplies = discussions.filter( + d => d.replies.length === 0 + ); + const discussionsWithYesReplies = discussions.filter( + d => d.replies.length !== 0 + ); + + return ( + <> + + + + ); }; const renderDiscussions = () => { @@ -348,7 +310,7 @@ const Discussions = ({ <> {t('heading')} @@ -414,7 +376,6 @@ const Discussions = ({ setDiscussionReplyID={setDiscussionReplyID} queryParams={queryParams} setInitQuestion={setInitQuestion} - setDiscussionStatusMessage={setDiscussionStatusMessage} setDiscussionType={setDiscussionType} /> ); diff --git a/src/views/ModelPlan/Table/formatActivity.tsx b/src/views/ModelPlan/Table/formatActivity.tsx deleted file mode 100644 index 8e37a4cad6..0000000000 --- a/src/views/ModelPlan/Table/formatActivity.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React from 'react'; -import { IconComment } from '@trussworks/react-uswds'; -import i18next from 'i18next'; - -import { GetModelPlans_modelPlanCollection_discussions as DiscussionType } from 'queries/types/GetModelPlans'; -import { formatDateLocal } from 'utils/date'; - -const formatRecentActivity = ( - date: string, - discussions: DiscussionType[] -): React.ReactNode => { - let discussionActivity; - - // Formatting date string of last updated model plan - const updated = `${i18next.t('home:requestsTable.updated')} ${formatDateLocal( - date, - 'MM/dd/yyyy' - )}`; - - // Filtering for answered/unanswered question on model plan - if (discussions.length > 0) { - const unansweredQuestions = discussions.filter( - (discussion: DiscussionType) => discussion.status === 'UNANSWERED' - ).length; - - // Checking/formatting for unanswered questions - let recentActivity: React.ReactNode = unansweredQuestions ? ( -

- {unansweredQuestions}{' '} - {i18next.t('home:requestsTable.unansweredQuestion')} - {unansweredQuestions > 1 && 's'} {/* Adding 's' for pluraltiy */} -

- ) : ( - <> - ); - - // Checking/formatting for answered questions - const answeredQuestions = discussions.length - unansweredQuestions; - - recentActivity = ( -
- {recentActivity}{' '} - {answeredQuestions > 0 && ( -

- {answeredQuestions}{' '} - {i18next.t('home:requestsTable.answeredQuestion')} - {answeredQuestions > 1 && 's'} -

- )} -
- ); - - // Formating any questions with Icon - discussionActivity = ( -
- {' '} - {recentActivity} -
- ); - } - - return ( -
- {updated} {discussionActivity} -
- ); -}; - -export default formatRecentActivity; diff --git a/src/views/ModelPlan/Table/index.tsx b/src/views/ModelPlan/Table/index.tsx index 62cf2a969d..5d66ae3015 100644 --- a/src/views/ModelPlan/Table/index.tsx +++ b/src/views/ModelPlan/Table/index.tsx @@ -14,6 +14,7 @@ import { import { useQuery } from '@apollo/client'; import { Button, + IconComment, IconStar, IconStarOutline, Table as UswdsTable @@ -39,7 +40,7 @@ import { ModelCategory, ModelPlanFilter } from 'types/graphql-global-types'; -import { formatDateUtc } from 'utils/date'; +import { formatDateLocal, formatDateUtc } from 'utils/date'; import CsvExportLink from 'utils/export/CsvExportLink'; import globalFilterCellText from 'utils/globalFilterCellText'; import { @@ -49,7 +50,6 @@ import { sortColumnValues } from 'utils/tableSort'; import { UpdateFavoriteProps } from 'views/ModelPlan/ModelPlanOverview'; -import formatRecentActivity from 'views/ModelPlan/Table/formatActivity'; type ModelPlansTableProps = | { @@ -285,8 +285,26 @@ const ModelPlansTable = ({ accessor: 'modifiedDts', Cell: ({ row, value }: any) => { const { discussions } = row.original; - const lastUpdated = value || row.original.createdDts; - return formatRecentActivity(lastUpdated, discussions); + const formattedUpdatedDate = `${homeT( + 'requestsTable.updated' + )} ${formatDateLocal( + value || row.original.createdDts, + 'MM/dd/yyyy' + )}`; + return ( + <> + {formattedUpdatedDate} + {discussions.length > 0 && ( +
+ {' '} + {discussions.length}{' '} + {i18next.t('discussions:discussionBanner.discussion', { + count: discussions.length + })} +
+ )} + + ); } }, paymentDate: { diff --git a/src/views/ModelPlan/TaskList/Basics/Milestones/__snapshots__/index.test.tsx.snap b/src/views/ModelPlan/TaskList/Basics/Milestones/__snapshots__/index.test.tsx.snap index 85dbf86d72..86edbbeaee 100644 --- a/src/views/ModelPlan/TaskList/Basics/Milestones/__snapshots__/index.test.tsx.snap +++ b/src/views/ModelPlan/TaskList/Basics/Milestones/__snapshots__/index.test.tsx.snap @@ -1168,3 +1168,1172 @@ exports[`Model Plan Documents page > matches snapshot 1`] = `
`; + +exports[`Model Plan Documents page > matches snapshot 2`] = ` + +
+ +

+ Model basics +

+

+ for My excellent plan that I just initiated +

+

+ If there's a question or field that is not applicable to your model or you don't currently know the answer, you may leave it blank. If you need help, ask a question using the link below. +

+
+
+ + + + + +
+
+
+
+
+

+ Anticipated high level timeline +

+
+
+

+ + Please be sure that the dates listed here are updated in the clearance calendar, if applicable. Contact the MINT Team at + + MINTTeam@cms.hhs.gov + + if you have any questions. + +

+
+
+
    +
  1. +
    +
    +
    + +
    + mm/dd/yyyy +
    +
    +
    + +
    + + + +
    +
    +
    +
    +
  2. +
  3. +
    + + Clearance + + +
    +
    + +
    + mm/dd/yyyy +
    +
    +
    + +
    + + + +
    +
    +
    + +
    + mm/dd/yyyy +
    +
    +
    + +
    + + + +
    +
    +
    +
    +
  4. +
  5. +
    +
    +
    + +
    + mm/dd/yyyy +
    +
    +
    + +
    + + + +
    +
    +
    +
  6. +
  7. +
    + + Application period + +
    +
    +
    + +
    + mm/dd/yyyy +
    +
    +
    + +
    + + + +
    +
    +
    + +
    + mm/dd/yyyy +
    +
    +
    + +
    + + + +
    +
    +
    +
  8. +
  9. +
    + + Performance period + + +
    +
    +
    + +
    + mm/dd/yyyy +
    +
    +
    + +
    + + + +
    +
    +
    + +
    + mm/dd/yyyy +
    +
    +
    + +
    + + + +
    +
    +
    +
  10. +
  11. +
    +
    +
    + +
    + mm/dd/yyyy +
    +
    +
    + +
    + + + +
    +
    +
    +
  12. +
+
+
+ + +
+
+
+ + + That is, the basic model would start at the earliest possible date but additional facets could be phased in at a later quarter. + +
+
+ + +
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+

+
+

+ Model Plan status +

+
+ + +

+

+
+

+
+
+
+ + +
+ +
+
+
+
+ + Page 3 of 3 + +
+
+
+`; diff --git a/src/views/ModelPlan/TaskList/Basics/Overview/__snapshots__/index.test.tsx.snap b/src/views/ModelPlan/TaskList/Basics/Overview/__snapshots__/index.test.tsx.snap index 5b36651c57..50811e1c3a 100644 --- a/src/views/ModelPlan/TaskList/Basics/Overview/__snapshots__/index.test.tsx.snap +++ b/src/views/ModelPlan/TaskList/Basics/Overview/__snapshots__/index.test.tsx.snap @@ -303,3 +303,306 @@ exports[`Model Plan Documents page > matches snapshot 1`] = `
`; + +exports[`Model Plan Documents page > matches snapshot 2`] = ` + +
+ +

+ Model basics +

+

+ for My excellent plan that I just initiated +

+

+ If there's a question or field that is not applicable to your model or you don't currently know the answer, you may leave it blank. If you need help, ask a question using the link below. +

+
+
+ + + + + +
+
+
+
+
+ +
+
+ + +
+
+ + +
+
+
+
+ +