Skip to content

feat: 🍰 Webapp, Backend – User Profile – Display, Select and Upload User Profile Header Image #3634

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 17 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion backend/src/db/factories.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,22 @@ Factory.define('user')
url: faker.internet.avatar(),
}),
)
/* Defaults to false since a lot of existing tests are based on finding a certain
amount of images without further checks causing them to fail when finding these
extra profile header images. A couple of users are given profile headers
explicitly in seed.js.
*/
.option('profileHeader', () => false)
.after(async (buildObject, options) => {
const [user, email, avatar] = await Promise.all([
const [user, email, avatar, profileHeader] = await Promise.all([
neode.create('User', buildObject),
neode.create('EmailAddress', { email: options.email }),
options.avatar,
options.profileHeader,
])
await Promise.all([user.relateTo(email, 'primaryEmail'), email.relateTo(user, 'belongsTo')])
if (avatar) await user.relateTo(avatar, 'avatar')
if (profileHeader) await user.relateTo(profileHeader, 'profileHeader')
return user
})

Expand Down
2 changes: 2 additions & 0 deletions backend/src/db/seed.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
},
{
email: '[email protected]',
profileHeader: Factory.build('image'),
},
),
Factory.build(
Expand All @@ -192,6 +193,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
},
{
email: '[email protected]',
profileHeader: Factory.build('image'),
},
),
Factory.build(
Expand Down
1 change: 1 addition & 0 deletions backend/src/middleware/softDelete/softDeleteMiddleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const obfuscate = async (resolve, root, args, context, info) => {
root.title = 'UNAVAILABLE'
root.slug = 'UNAVAILABLE'
root.avatar = null
root.profileHeader = null
root.about = 'UNAVAILABLE'
root.name = 'UNAVAILABLE'
root.image = null
Expand Down
11 changes: 11 additions & 0 deletions backend/src/middleware/softDelete/softDeleteMiddleware.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ beforeAll(async () => {
avatar: Factory.build('image', {
url: '/some/offensive/avatar.jpg',
}),
profileHeader: Factory.build('image', {
url: '/some/offensive/profileHeader.jpg',
}),
},
),
neode.create('Category', {
Expand Down Expand Up @@ -225,6 +228,9 @@ describe('softDeleteMiddleware', () => {
avatar {
url
}
profileHeader {
url
}
}
}
}
Expand Down Expand Up @@ -270,6 +276,10 @@ describe('softDeleteMiddleware', () => {
expect(subject.avatar).toEqual({
url: expect.stringContaining('/some/offensive/avatar.jpg'),
}))
it('display profile header', () =>
expect(subject.profileHeader).toEqual({
url: expect.stringContaining('/some/offensive/profileHeader.jpg'),
}))
})

describe('Post', () => {
Expand Down Expand Up @@ -308,6 +318,7 @@ describe('softDeleteMiddleware', () => {
it('obfuscates slug', () => expect(subject.slug).toEqual('UNAVAILABLE'))
it('obfuscates about', () => expect(subject.about).toEqual('UNAVAILABLE'))
it('obfuscates avatar', () => expect(subject.avatar).toEqual(null))
it('obfuscates profile header', () => expect(subject.profileHeader).toEqual(null))
})

describe('Post', () => {
Expand Down
6 changes: 6 additions & 0 deletions backend/src/models/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ export default {
target: 'Image',
direction: 'out',
},
profileHeader: {
type: 'relationship',
relationship: 'PROFILE_HEADER_IMAGE',
target: 'Image',
direction: 'out',
},
deleted: { type: 'boolean', default: false },
disabled: { type: 'boolean', default: false },
role: { type: 'string', default: 'user' },
Expand Down
2 changes: 1 addition & 1 deletion backend/src/schema/resolvers/images/images.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ const uploadImageFile = async (upload, uploadCallback) => {
const sanitizeRelationshipType = (relationshipType) => {
// Cypher query language does not allow to parameterize relationship types
// See: https://github.com/neo4j/neo4j/issues/340
if (!['HERO_IMAGE', 'AVATAR_IMAGE'].includes(relationshipType)) {
if (!['HERO_IMAGE', 'AVATAR_IMAGE', 'PROFILE_HEADER_IMAGE'].includes(relationshipType)) {
throw new Error(`Unknown relationship type ${relationshipType}`)
}
}
Expand Down
1 change: 1 addition & 0 deletions backend/src/schema/resolvers/passwordReset.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ beforeEach(() => {
})

beforeAll(() => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tirokk Authored by Tirokk
Jun 18, 2020


Outdated (history rewrite) - original diff


@@ -25,6 +25,7 @@ beforeEach(() => {
 })
 
 beforeAll(() => {
+  cleanDatabase()

Nice you added this! 😍

But the await is missing:

await cleanDatabase()

would be correct.

cleanDatabase()
const { server } = createServer({
context: () => {
return {
Expand Down
4 changes: 3 additions & 1 deletion backend/src/schema/resolvers/statistics.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ const statisticsQuery = gql`
}
}
`
beforeAll(() => {
beforeAll(async () => {
// Clean the database so no artifacts from other test files are interfering.
await cleanDatabase()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tirokk Authored by Tirokk
Jun 18, 2020


Outdated (history rewrite) - original diff


@@ -21,7 +21,9 @@ const statisticsQuery = gql`
     }
   }
 `
-beforeAll(() => {
+beforeAll(async () => {
+  // Clean the database so no artifacts from other test files are interfering.
+  await cleanDatabase()

πŸ˜πŸ‘πŸΌ

authenticatedUser = undefined
const { server } = createServer({
context: () => {
Expand Down
9 changes: 9 additions & 0 deletions backend/src/schema/resolvers/user_management.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ describe('currentUser', () => {
avatar {
url
}
profileHeader {
url
}
email
role
}
Expand Down Expand Up @@ -142,6 +145,9 @@ describe('currentUser', () => {
avatar: Factory.build('image', {
url: 'https://s3.amazonaws.com/uifaces/faces/twitter/jimmuirhead/128.jpg',
}),
profileHeader: Factory.build('image', {
url: 'https://s3.amazonaws.com/uifaces/faces/twitter/hellofeverrrr/128.jpg',
}),
},
)
const userBearerToken = encode({ id: 'u3' })
Expand All @@ -156,6 +162,9 @@ describe('currentUser', () => {
avatar: Factory.build('image', {
url: 'https://s3.amazonaws.com/uifaces/faces/twitter/jimmuirhead/128.jpg',
}),
profileHeader: Factory.build('image', {
url: 'https://s3.amazonaws.com/uifaces/faces/twitter/hellofeverrrr/128.jpg',
}),
email: '[email protected]',
name: 'Matilde Hermiston',
slug: 'matilde-hermiston',
Expand Down
8 changes: 7 additions & 1 deletion backend/src/schema/resolvers/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,9 @@ export default {
},
UpdateUser: async (_parent, params, context, _resolveInfo) => {
const { termsAndConditionsAgreedVersion } = params
const { avatar: avatarInput } = params
const { avatar: avatarInput, profileHeader: profileHeaderInput } = params
delete params.avatar
delete params.profileHeader
if (termsAndConditionsAgreedVersion) {
const regEx = new RegExp(/^[0-9]+\.[0-9]+\.[0-9]+$/g)
if (!regEx.test(termsAndConditionsAgreedVersion)) {
Expand All @@ -165,6 +166,9 @@ export default {
if (avatarInput) {
await mergeImage(user, 'AVATAR_IMAGE', avatarInput, { transaction })
}
if (profileHeaderInput) {
await mergeImage(user, 'PROFILE_HEADER_IMAGE', profileHeaderInput, { transaction })
}
return user
})
try {
Expand Down Expand Up @@ -235,6 +239,7 @@ export default {
log(deleteUserTransactionResponse)
const [user] = deleteUserTransactionResponse.records.map((record) => record.get('user'))
await deleteImage(user, 'AVATAR_IMAGE', { transaction })
await deleteImage(user, 'PROFILE_HEADER_IMAGE', { transaction })
return user
})
try {
Expand Down Expand Up @@ -291,6 +296,7 @@ export default {
},
hasOne: {
avatar: '-[:AVATAR_IMAGE]->(related:Image)',
profileHeader: '-[:PROFILE_HEADER_IMAGE]->(related:Image)',
invitedBy: '<-[:INVITED]-(related:User)',
location: '-[:IS_IN]->(related:Location)',
},
Expand Down
40 changes: 26 additions & 14 deletions backend/src/schema/resolvers/users.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -341,11 +341,17 @@ describe('DeleteUser', () => {
beforeEach(async () => {
variables = { id: ' u343', resource: [] }

user = await Factory.build('user', {
name: 'My name should be deleted',
about: 'along with my about',
id: 'u343',
})
user = await Factory.build(
'user',
{
name: 'My name should be deleted',
about: 'along with my about',
id: 'u343',
},
{
profileHeader: Factory.build('image'),
},
)
})

describe('authenticated as Admin', () => {
Expand Down Expand Up @@ -496,8 +502,8 @@ describe('DeleteUser', () => {
).resolves.toMatchObject(expectedResponse)
})

it('deletes user avatar and post hero images', async () => {
await expect(neode.all('Image')).resolves.toHaveLength(22)
it('deletes user avatar, profile header and post hero images', async () => {
await expect(neode.all('Image')).resolves.toHaveLength(23)
await mutate({ mutation: deleteUserMutation, variables })
await expect(neode.all('Image')).resolves.toHaveLength(20)
})
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tirokk Authored by Tirokk
Jun 18, 2020


Outdated (history rewrite) - original diff


@@ -496,8 +502,8 @@ describe('DeleteUser', () => {
                 ).resolves.toMatchObject(expectedResponse)
               })
 
-              it('deletes user avatar and post hero images', async () => {
-                await expect(neode.all('Image')).resolves.toHaveLength(22)
+              it('deletes user avatar, profile header and post hero images', async () => {
+                await expect(neode.all('Image')).resolves.toHaveLength(23)
                 await mutate({ mutation: deleteUserMutation, variables })
                 await expect(neode.all('Image')).resolves.toHaveLength(20)
               })

Thanks to add this test! πŸ’«
Cool 😎

Expand Down Expand Up @@ -627,11 +633,17 @@ describe('DeleteUser', () => {
beforeEach(async () => {
variables = { id: 'u343', resource: [] }

user = await Factory.build('user', {
name: 'My name should be deleted',
about: 'along with my about',
id: 'u343',
})
user = await Factory.build(
'user',
{
name: 'My name should be deleted',
about: 'along with my about',
id: 'u343',
},
{
profileHeader: Factory.build('image'),
},
)
await Factory.build(
'user',
{
Expand Down Expand Up @@ -792,8 +804,8 @@ describe('DeleteUser', () => {
).resolves.toMatchObject(expectedResponse)
})

it('deletes user avatar and post hero images', async () => {
await expect(neode.all('Image')).resolves.toHaveLength(22)
it('deletes user avatar, profile header and post hero images', async () => {
await expect(neode.all('Image')).resolves.toHaveLength(23)
await mutate({ mutation: deleteUserMutation, variables })
await expect(neode.all('Image')).resolves.toHaveLength(20)
})
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tirokk Authored by Tirokk
Jun 18, 2020


Outdated (history rewrite) - original diff


@@ -792,8 +804,8 @@ describe('DeleteUser', () => {
                 ).resolves.toMatchObject(expectedResponse)
               })
 
-              it('deletes user avatar and post hero images', async () => {
-                await expect(neode.all('Image')).resolves.toHaveLength(22)
+              it('deletes user avatar, profile header and post hero images', async () => {
+                await expect(neode.all('Image')).resolves.toHaveLength(23)
                 await mutate({ mutation: deleteUserMutation, variables })
                 await expect(neode.all('Image')).resolves.toHaveLength(20)
               })

Great! πŸ’«

Expand Down
2 changes: 2 additions & 0 deletions backend/src/schema/types/type/User.gql
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type User {
email: String! @cypher(statement: "MATCH (this)-[:PRIMARY_EMAIL]->(e:EmailAddress) RETURN e.email")
slug: String!
avatar: Image @relation(name: "AVATAR_IMAGE", direction: "OUT")
profileHeader: Image @relation(name: "PROFILE_HEADER_IMAGE", direction: "OUT")
deleted: Boolean
disabled: Boolean
role: UserGroup!
Expand Down Expand Up @@ -192,6 +193,7 @@ type Mutation {
email: String
slug: String
avatar: ImageInput
profileHeader: ImageInput
locationName: String
about: String
termsAndConditionsAgreedVersion: String
Expand Down
1 change: 1 addition & 0 deletions cypress/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ The following features will be implemented. This gets done in three steps:

* Upload and Change Avatar
* Upload and Change Profile Picture
* Upload and Change Profile Header Image
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tirokk Authored by Tirokk
Jun 18, 2020


Outdated (history rewrite) - original diff


@@ -38,6 +38,7 @@ The following features will be implemented. This gets done in three steps:
 
 * Upload and Change Avatar
 * Upload and Change Profile Picture
+* Upload and Change Profile Header Image

Thanks very much that you thought about changing this doc as well. 😍

* Edit Social Media Accounts
* Edit Locale information
* Show and delete Bookmarks \(later\)
Expand Down
28 changes: 27 additions & 1 deletion cypress/integration/common/profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,39 @@ Then("I should be able to change my profile picture", () => {
);
});

Then("I should be able to change my profile header picture", () => {
const headerUpload = "onourjourney.png";

cy.fixture(headerUpload, "base64").then(fileContent => {
cy.get("#profileHeaderDropzone").upload(
{ fileContent, fileName: headerUpload, mimeType: "image/png" },
{ subjectType: "drag-n-drop", force: true }
);
});
cy.get(".profile-header-image")
.should("have.attr", "src")
.and("contains", "onourjourney");
cy.contains(".iziToast-message", "Upload successful").should(
"have.length",
1
);
});

When("I visit another user's profile page", () => {
cy.openPage("profile/peter-pan");
});

Then("I cannot upload a picture", () => {
Then("I cannot upload a profile picture", () => {
cy.get(".base-card")
.children()
.should("not.have.id", "customdropzone")
.should("have.class", "user-avatar");
});


Then("I cannot upload a profile header image", () => {
cy.get(".base-card")
.children()
.should("not.have.id", "profileHeaderDropzone")
.should("have.class", "profile-header");
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DriesCruyskens Authored by DriesCruyskens
Jun 26, 2020


Outdated (history rewrite) - original diff


@@ -28,9 +46,17 @@ When("I visit another user's profile page", () => {
   cy.openPage("profile/peter-pan");
 });
 
-Then("I cannot upload a picture", () => {
+Then("I cannot upload a profile picture", () => {
   cy.get(".base-card")
     .children()
     .should("not.have.id", "customdropzone")
     .should("have.class", "user-avatar");
 });
+
+
+Then("I cannot upload a profile header image", () => {
+  cy.get(".base-card")
+    .children()
+    .should("not.have.id", "profileHeaderDropzone")
+    .should("have.class", "profile-header-card");

I suspect the fix you did changing profile-header to profile-header-card was wrong after all and the errors you thought it caused had in fact different causes. Since this test finds the element with .base-card class and then analyzes its children, its easy to see why it can't find profile-header-card. The test in its original state was in fact checking the child with the profile-header class:

image

tldr; The error regarding this test should be fixed by changing line 61 back to

Suggested change
.should("have.class", "profile-header");
.should("have.class", "profile-header");

});
18 changes: 18 additions & 0 deletions cypress/integration/user_profile/UploadUserProfileHeader.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Feature: Upload UserProfile Header
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tirokk Authored by Tirokk
Jun 18, 2020


Outdated (history rewrite) - original diff


@@ -0,0 +1,18 @@
+Feature: Upload UserProfile Header

Hey, very good you add this e2e test! πŸ€—πŸ˜

As a user
I would like to be able to add a profile header pic to my profile
So that I can personalize my profile


Background:
Given I have a user account

Scenario: Change my UserProfile Header
Given I am logged in
And I visit my profile page
Then I should be able to change my profile header picture

Scenario: Unable to change another user's header images
Given I am logged in with a "user" role
And I visit another user's profile page
Then I cannot upload a profile header image
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ Feature: Upload UserProfile Image
Scenario: Unable to change another user's avatar
Given I am logged in with a "user" role
And I visit another user's profile page
Then I cannot upload a picture
Then I cannot upload a profile picture
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tirokk Authored by Tirokk
Jun 18, 2020


Outdated (history rewrite) - original diff


@@ -15,4 +15,4 @@ Feature: Upload UserProfile Image
   Scenario: Unable to change another user's avatar
     Given I am logged in with a "user" role
     And I visit another user's profile page
-    Then I cannot upload a picture
\ No newline at end of file
+    Then I cannot upload a profile picture

Cool you’ve clarified this as well! πŸ˜ŽπŸ‘πŸΌ

Loading