Skip to content
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

Pin repos on profile #30961

Open
wants to merge 16 commits into
base: main
Choose a base branch
from

Conversation

carlosfelgueiras
Copy link
Contributor

@carlosfelgueiras carlosfelgueiras commented May 13, 2024

Fixes #10375

This PR implements the ability for users and organizations to pin repositories to their profiles.

At a given time, each user or organization can have, at most, 6 repositories pinned. A user can pin any repository that it has read permissions to. Organizations can only have pinned to their profiles repositories owned by themselves, only administrators of an organization can pin repositories to the org's profile.

The pins are stored on a table called repo_pin. When a repository is deleted, all its pins are automatically deleted. When a user loses visibility to a repository, the pin is not automatically removed. Instead, invalid pins will automatically be removed the next time someone does a read or write operation to said user's pins.

When a different user is visiting someone's profile, only the repositories that the visiting user can see are shown on the pins, to avoid leaking private information.

Used assets (unpin icon and pined repo cards) from #19831.


Profile of a given user seen from its perspective, showing its pinned repos. Profile of a given user seen from another user perspective, showing its pinned repos. Header of the main page of a repository, showing the option to pin the repo. Header of the main page of a repository owned by an organization which the current user is admin, showing the options for pinning to the user or org profile. Header of the main page of a repository showing the disabled pin button, since the user has reached the max limit of pinned repos.

carlosfelgueiras and others added 4 commits May 10, 2024 17:35
- Created the pin model, and basic CRUD operations.
- Implemented checks for lost of visibility of owner, to automatically
  delete the repository.
- Implemented check for the lack of visibility of a viewer, when
  requesting the repos of another user, excluding those repositories
  from the list that is returned.
- Added the deletion of all the pins of a repository when it is deleted.
- Implemented the ability for a user to pin on the profile of an org
  user, checking if the user is admin of the org, and the org is owner
  of the repo.

Co-authored-by: Daniel Carvalho <daniel.m.carvalho@tecnico.ulisboa.pt>
- Added list and count of pinned repos to the context data
  of the routes of a user and org profile.
- Added a template for the list of pinned repo cards.
- Included said template in the user and org profile templates.

Co-authored-by: Daniel Carvalho <daniel.m.carvalho@tecnico.ulisboa.pt>
- Added the unpin octicon svg from a previous attempt to implement this
  feature.
- Added the template for the button/set of buttons for pinning/unpinning
  a repo.
- Added the use of said template in the header of a repo main page.
- Added the routes for the POST requests for pinning/unpinning to
  user/org.

Co-authored-by: Daniel Carvalho <daniel.m.carvalho@tecnico.ulisboa.pt>
- Created unit tests that check the basic functionality of the model
  functions
- Created integration tests that check the functionality of the POST
  routes, verifying the effects directly on the database.

Co-authored-by: Daniel Carvalho <daniel.m.carvalho@tecnico.ulisboa.pt>
@GiteaBot GiteaBot added the lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. label May 13, 2024
@pull-request-size pull-request-size bot added the size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. label May 13, 2024
@github-actions github-actions bot added modifies/translation modifies/go Pull requests that update Go code modifies/templates This PR modifies the template files labels May 13, 2024
models/repo/pin.go Outdated Show resolved Hide resolved
routers/web/repo/view.go Outdated Show resolved Hide resolved
@lunny lunny added this to the 1.23.0 milestone May 13, 2024
{{if .IsPrivate}}
<span class="ui basic label">{{ctx.Locale.Tr "repo.desc.private"}}</span>
{{end}}
{{end}}
Copy link
Member

Choose a reason for hiding this comment

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

Do we have this label sub-templated yet? If not, I suggest creating templates/shared/repo/label.tmpl which accepts a Repo as its only argument.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think that sub-template exists yet. At least the repo main page's header does not use any template for that part and I don't find any label template related to repos. I will create it then.

@silverwind
Copy link
Member

Can you reduce the space between these card to half of what it is currently?

image

@KN4CK3R KN4CK3R changed the title Pin repos on profile (Fixes #10375) Pin repos on profile May 13, 2024
Co-authored-by: silverwind <me@silverwind.io>
@silverwind silverwind added the type/feature Completely new functionality. Can only be merged if feature freeze is not active. label May 15, 2024
@carlosfelgueiras
Copy link
Contributor Author

@silverwind I managed to reduce the space between the cards to half the size on the latest commit, but I don't know if my changes follow the best practices. Can you comment on that?

@carlosfelgueiras
Copy link
Contributor Author

This is how it looks now.

image

models/repo/pin.go Outdated Show resolved Hide resolved
}

// CleanupPins iterates over the repos pinned by a user and removes
// the invalid pins. (Needs to be called everytime before we read/write a pin)
Copy link
Contributor

Choose a reason for hiding this comment

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

(Needs to be called everytime before we read/write a pin)

This function seems a bit strange. I think it is not the correct way to handle this problem.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This approach where we clean the invalid pins only when it is needed was suggested in the original issue thread (#10375 (comment)), so we just followed that suggestion since it seemed to be agreed upon. If people want to discuss this further, we can implement what is desired later, after a consensus is reached.

Copy link
Contributor

Choose a reason for hiding this comment

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

It is something like this #26530
At first, we do not have permission system, and stored all these permission related data in DB.
Actually, it should be generated dynamically or there will be more and more bugs.
The comment you mentioned is about 2 years ago, which is a solution for this problem, but I don't think it is a good solution.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe we should wait for the maintainers to give their opinion. I guess they are currently focused on 1.22.0, but after the release they could comment on this.

@@ -1168,3 +1177,31 @@ func Forks(ctx *context.Context) {

ctx.HTML(http.StatusOK, tplForks)
}

func loadPinData(ctx *context.Context) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe we can cache pin data

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see a good reason for caching that data, but I'm not confident in implementing this myself. I'm not too accustomed to this codebase and, since some of the pin data can be sensitive data (since some pinned repos can be private), that would be a hard task to do without any guidance.

Copy link
Contributor

@yp05327 yp05327 May 22, 2024

Choose a reason for hiding this comment

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

I will ask @lunny.

Copy link
Member

Choose a reason for hiding this comment

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

I think it's unnecessary to cache these pinned repositories.

services/user/pin.go Outdated Show resolved Hide resolved
services/user/pin.go Outdated Show resolved Hide resolved
services/user/pin.go Outdated Show resolved Hide resolved
func HasPermsToPin(ctx *context.Context, u *user_model.User, r *repo_model.Repository) bool {
// If user is an organization, it can only pin its own repos
if u.IsOrganization() {
return r.OwnerID == u.ID
Copy link
Contributor

Choose a reason for hiding this comment

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

the user here is doer not the owner of the repo, also in CanPin, it is better to rename u to doer
so u.IsOrganization() will always be false here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure that I'm understanding what you are saying here. Since there are two pin actions (pin to the doer's profile and pin to the owner org's profile), the user passed can be an organization. Also, this function is called in CleanupPins to check if any repo pinned to the org's profile has been moved to another org.

Copy link
Contributor

Choose a reason for hiding this comment

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

doer is the logined user, and organization user is also defined as user in source codes, but we can not login as an organization user. So doer is always an individual user and u.IsOrganization() will always be false here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, the thing is that this function is not only called with a Doer. It's just a function that checks for the base requirements that a user needs to fulfill to have the repository pinned to their profile. When a user tries to pin a repository to their own profile, the user in this function will be the Doer. But that user can also pin the repo to an organization (meaning that the user in this function will be the org, since that is the target profile for the pin). This function is also used later to check if the user has lost visibility to the repo, or (in this case for organizations) if the repo has been transferred to another org.

Copy link
Contributor

Choose a reason for hiding this comment

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

But that user can also pin the repo to an organization (meaning that the user in this function will be the org, since that is the target profile for the pin).

This is not a permission check.
The permission check should be
(1) whether doer (the user tries to pin the repo to an org) is the owner/admin of the org (2) which should be the repo's owner.
You only checked the second one, so if there's a public org and a public repo (belongs to this org), the user who is not the member of this org, can access this repo's, and can pin this repo to the org, which should not happen.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, maybe that is a naming issue. This function was intended only to check if a given user has perms to have a given repository pinned to their profile. The check that the Doer has perms on the org to pin in its profile is done in assertUserOrgPerms which is only called if the action done is pinning/unpinning on the org profile. Maybe this code can be refactored or at least the functions can be renamed.

@lunny
Copy link
Member

lunny commented May 21, 2024

Thank you for your contribution. I will do a review after 1.22.0 is released.

@yp05327
Copy link
Contributor

yp05327 commented May 22, 2024

Can you also upload a screenshot of organization's home(profile) page?
The design in #10375 (comment) seems not good enough.
I think the block in the below should be moved to the top of this page, as pined repos should be a part of repositories tab which is similar to individual user's profile page.
image

@carlosfelgueiras
Copy link
Contributor Author

Can you also upload a screenshot of organization's home(profile) page? The design in #10375 (comment) seems not good enough. I think the block in the below should be moved to the top of this page, as pined repos should be a part of repositories tab which is similar to individual user's profile page. image

Yes, we also thought about that and changed it so the pinned repo cards are inside the repositories tab.
image

@JakobDev
Copy link
Contributor

Is there a way to customize the order?

models/repo/pin.go Outdated Show resolved Hide resolved

func loadPinData(ctx *context.Context) error {
// First, cleanup any pins that are no longer valid
err := user_service.CleanupPins(ctx, ctx.Doer)
Copy link
Member

Choose a reason for hiding this comment

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

Why cleanup pins when user home page be visited? Looks wired.

Copy link
Contributor

Choose a reason for hiding this comment

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

We are cleaning the pins to avoid an edge case.
If we have a user1, with 6 pins, being one of those pins a repo belonging to user2. If user 2 changes the repo visibility to private, user1 can no longer have it as a pin. If user1 now visited a new repo page, without visiting his own homepage, the pin button would be disabled because he already has the maximum number of pins. However, if we clean the pins, the pin to the repo of user2 will be deleted, and the button will be enabled.

Copy link
Contributor

Choose a reason for hiding this comment

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

When changing the visibility and user has no permission to access this pin, means user2 forcibly unpinned it, so this should be handled when user2 changes the repo visibility, not the moment user visiting the repo home page.
Also for other cases, like remove the user as the member of an organization and so on.

// Checks if the user has permission to have the repo pinned in it's profile.
func HasPermsToPin(ctx *context.Context, u *user_model.User, r *repo_model.Repository) bool {
// If user is an organization, it can only pin its own repos
if u.IsOrganization() {
Copy link
Member

Choose a reason for hiding this comment

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

I think u should be renamed doer. This should check whether the doer can pin the repository into doer's profile? Or we need another parameter which is the pinning target user/org.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think the best approach would be to add a parameter that is the target and move all checks for permissions to this function.

@DanielMatiasCarvalho
Copy link
Contributor

@JakobDev
No, we currently cannot change the order. We thought it would be better if there were a basic implementation of the pin feature, which is the focus of this PR. Later, more capabilities, like ordering, could be added to it.

@JakobDev
Copy link
Contributor

I think ordering is a basic feature that belongs to this PR before merging

@lunny
Copy link
Member

lunny commented May 27, 2024

We also need to handle user/org deletions.

@carlosfelgueiras
Copy link
Contributor Author

We also need to handle user/org deletions.

We forgot about that. Now it's done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. modifies/go Pull requests that update Go code modifies/templates This PR modifies the template files modifies/translation size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. type/feature Completely new functionality. Can only be merged if feature freeze is not active.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Proposal / Feature Request: Pin Repositories
7 participants