= TrustedProjectMembers =
* '''Launchpad entry:''' https://blueprints.launchpad.net/launchpad-registry/+spec/trusted-project-members
* '''Created:''' 2007-06-19 by CurtisHovey
* '''Contributors:''' FrancisJLacoste, kiko, BjornT, statik, allenap
* '''Status:''' DraftSpecification
* '''Obsoletes:''' BadgesSpec AnswerBadgesAndRanks [[https://blueprints.launchpad.net/launchpad-answers/+spec/badges-and-ranks|badges-and-ranks]] OfficalProjectMembers
== Summary ==
As the owner of a project
I want my company personnel to have access to private project data
so that the whole company can see the bugs and branches, without giving them responsibility to manage the project.
This document is about defining persons who are trusted project members. Two general problems are solvable when we can identify who is trusted in a project. Who can access private data like bugs and branches? Who represents the project in public messages.
== Rationale ==
Project may have private data that all it's members need to access. For example a company will want all it's employees to see private bugs and branches, without placing each person in the team that owns the project. These members are trusted to see all information, but are not taking on the responsibility to manage the project, triage bugs, and plan releases.
Projects need to identify their trusted members and their messages to users. Users of Launchpad need to determine the importance of a message and its author when making decisions. Within a project, Launchpad will display the project's icon with the official project member and his messages. The icon will act as a badge identifying the official aspect of the person or message to users.
== Use cases ==
There are three stories driving this feature.
=== Trusted members can see private data ===
As the owner of a project
I want my company personnel to have access to private project data
so that the whole company can see the bugs and branches, without giving them responsibility to manage the project.
=== Project badges convey trust ===
As a user of launchpad
I want to see project badges in user comments next to trusted project members
so that I can identify the people who represent the project.
=== Adding someone to the official team make him an official member ===
As an administrator for a company team
I want to add a user to a single team to give access to private data
so that I can give access to all my company projects that have set the company team as the trusted members.
== Design ==
Launchpad defines several official roles for a project. There are
undefined roles used by projects, but there is no means to identify who
the people are in the roles that Launchpad does not understand. We can
allow projects to designate an official moderated team for official
members. I think a team is appropriate because it was intended as an
administrative mechanism, and it provides the required features of
adding and removing users under moderation. Projects are already using
moderated teams to control official membership--the concept will be
familiar to Launchpad
Badges relate to the PersonNamePresentation spec. The badge is the
project icon. The general rule may be summarized as: When listing members of
a project, and showing the owner of a message, the official members
will display their official project badge.
=== IHasOfficials and IHasAppointedOfficial ===
The three pillar objects `IDistribution`, `IProduct`, and `IProject` require
a mechanism to identify its officials, and a means to answer the
question. 'is this person an official?'. This problem has similarities
to Driver concept defined by `IHasDrivers` and `IHasAppointedDriver`,
but the use cases have a broader scope and have a greater involvement
with the IPerson object.
An IPerson may be an official for four reasons:
1. He is the project owner.
2. He is the project driver.
3. He is a member of the drivers team.
4. He is a member of the trusted team.
The officials are an `ICrowd` composed of IPersons and `ITeams. An `ICrowd`
simplifies the rules for working with the four conditions, and
alleviates the need to implement a mechanism that places the owner and
driver(s) in the officials team. The officials are represented as an
attribute of the pillar object.
Objects that have officials also have `isAnOfficial(person)` which tests
if the `IPerson` is a member of the officials crowd.
Objects that have officials have an officials_team properties for
Persons who are not in any defined roles (owner or driver), yet play a
role in stewarding the project. The team may only contain persons--a
detail left up to the implementation. As a `ICrowd` represents all
officials, the implementation does not need to reconcile the owner and
drivers of the project with the officials_team.
{{{#!python
class IHasOfficials(Interface):
"""An object that has officials.
Officials represent their project in messages. They are granted
additional responsibilities by Launchpad applications.
"""
officials = Attribute(_("A crowd of officials"))
def isAnOfficial(person):
"""Return True when person is an official, otherwise False.
A person is an official when he is the project owner, driver, a
member of the drivers team, or a member of the officials team.
"""
class IHasAppointedOfficial(Interface):
"""An object that has an appointed official.
An official is a team. It cannot have Teams as members.
"""
officials_team = Choice(
title=_("Officials team"),
description=_("The team that projects' officials belong too."),
required=False,
vocabulary='ValidTeam')
}}}
IDistribution, IProduct, and IProject will inherit IHasOfficials and
IHasAppointedOfficial.
{{{#!python
class IDistribution(IHasAppointedDriver, IHasDrivers, IHasOwner,
IHasOfficials, IHasAppointedOfficial, IBugTarget,
ISpecificationTarget, IHasSecurityContact,
IKarmaContext, IHasMentoringOffers, IHasSprints,
IHasTranslationGroup):
...
}}}
{{{#!python
class IProduct(IHasAppointedDriver, IHasDrivers, IHasOwner,
IHasOfficials, IHasAppointedOfficial, IBugTarget,
ISpecificationTarget, IHasSecurityContact, IKarmaContext,
IHasSprints, IHasMentoringOffers, IHasLogo, IHasMugshot,
IHasIcon, IHasBranchVisibilityPolicy, IHasTranslationGroup):
...
}}}
{{{#!python
class IProject(IHasAppointedDriver, IHasOwner, IHasOfficials,
IHasAppointedOfficial, IBugTarget, IHasSpecifications,
IKarmaContext, IHasSprints, IHasMentoringOffers, IHasIcon,
IHasLogo, IHasMugshot, IHasBranchVisibilityPolicy,
IHasTranslationGroup)
...
}}}
== Implementation ==
There are challenges in the implementation. The display of a Person's
icon is always from the context of the Person. Persons do not join
Pillars; they contribute to them. There is no natural way to ask
'what pillars is this person a member of?' to refine the question
to 'what pillars is this person a official of?'.
* `Person.myactivememberships` is about teams, not Pillars.
* `Person.getProjectsAndCategoriesContributedTo` is about karma, not Pillars
The ILaunchbag contains the pillar objects (`Distributions`,
`Projects` and `Products`) that represent projects, and it is accessible
to the TALES formatter that shows the icons.
=== Schema changes ===
Distribution, Product, and Project require an additional column
to link to the their officials team.
{{{
-- Add the officials team to Distribution, Product, and Project.
-- This team is implemented similar to the driver team.
ALTER TABLE Distribution
ADD COLUMN officials_team int,
ADD CONSTRAINT distribution_officials_team_fk FOREIGN KEY (officials_team) REFERENCES person(id);
CREATE INDEX distribution__officials_team__idx ON distribution USING btree (officials_team) WHERE (officials_team IS NOT NULL);
ALTER TABLE Product
ADD COLUMN officials_team int,
ADD CONSTRAINT product_officials_team_fk FOREIGN KEY (officials_team) REFERENCES person(id);
CREATE INDEX product__officials_team__idx ON product USING btree (officials_team) WHERE (officials_team IS NOT NULL);
ALTER TABLE Project
ADD COLUMN officials int,
ADD CONSTRAINT project_officials_team_fk FOREIGN KEY (officials_team) REFERENCES person(id);
CREATE INDEX project__officials_team__idx ON project USING btree (officials_team) WHERE (officials_team IS NOT NULL);
}}}
=== Model changes ===
`Distribution`, `Product`, and `Project` will inherit from a new mixin class
named `HasOfficialsMixin`. The mixin will implement the properties and
method required by the `IHasOfficials` and `IHasAppointedOfficials`
interfaces.
{{{#!python
class HasOfficialsMixin:
"""A mixin class that implements IHasOfficials and IHasAppointedOfficial.
This class provides the the properties and methods to manage officials.
"""
officials_team = ForeignKey(
foreignKey="Person", dbName="officials_team",
notNull=False, default=None)
...
@property
def officials(self):
"""See `IHasOfficials`."""
return (
ICrowd(self.owner) + ICrowd(self.driver) + ICrowd(officials_team))
def isAnOfficial(self, person):
"""See `IHasOfficials`."""
if person.isTeam():
return False
return person in self.officials
...
}}}
{{{#!python
class Distribution(SQLBase, BugTargetBase, HasOfficialsMixin,
HasSpecificationsMixin, HasSprintsMixin, KarmaContextMixin,
QuestionTargetMixin):
...
}}}
{{{
class Product(SQLBase, BugTargetBase, HasOfficialsMixin,
HasSpecificationsMixin, HasSprintsMixin, KarmaContextMixin,
BranchVisibilityPolicyMixin, QuestionTargetMixin):
...
}}}
{{{#!python
class Project(SQLBase, BugTargetBase, HasOfficialsMixin,
HasSpecificationsMixin, HasSprintsMixin, KarmaContextMixin,
BranchVisibilityPolicyMixin):
...
}}}
=== View changes ===
`Distribution`, `Product`, and `Project` each requires a view to assign
the officials_team, and a link in their menu to that page. The view
code for each pillar will provide the link.
{{{#!python
class DistributionOverviewMenu(ApplicationMenu):
...
links = ['edit', 'branding', 'driver', 'officials_team', ...]
...
@enabled_with_permission('launchpad.Edit')
def officials_team(self):
text = 'Appoint the official team'
summary = 'Persons who are trusted to represent the project.'
return Link('+officials_team', text, summary, icon='edit')
...
class ProductOverviewMenu(ApplicationMenu):
...
links = ['edit', 'branding', 'driver', 'officials_team', ...]
...
@enabled_with_permission('launchpad.Edit')
def officials_team(self):
text = 'Appoint the official team'
summary = 'Persons who are trusted to represent the project.'
return Link('+officials_team', text, summary, icon='edit')
...
class ProjectOverviewMenu(ApplicationMenu):
...
links = ['edit', 'branding', 'driver', 'officials_team', ...]
...
@enabled_with_permission('launchpad.Edit')
def officials_team(self):
text = 'Appoint the official team'
summary = 'Persons who are trusted to represent the project.'
return Link('+officials_team', text, summary, icon='edit')
...
}}}
The edit form is provided by `AppointOfficialsView` which is a
class in the browser.officials package. The view is used by all three
pillar objects.
{{{#!python
class AppointOfficialsView(LaunchpadEditFormView):
"""Browser view for appointing the officials team to an object."""
field_names = ['officials']
@property
def schema(self):
"""Return the schema that is the most specific extension of
IHasAppointedDriver
"""
assert IHasAppointedOfficials.providedBy(self.context), (
"context should provide IHasAppointedOfficials.")
for interface in providedBy(self.context):
if interface.isOrExtends(IHasAppointedOfficials):
# XXX sinzui 2007-07-22 bug=84940:
# removeSecurityProxy() is a needed because formlib
# does not use the security-aware isinstance() wrapper.
return removeSecurityProxy(interface)
@action('Change', name='change')
def change_action(self, action, data):
officials_team = data['officials_team']
self.updateContextFromData(data)
if officials_team is not None:
assert driver.isTeam(), (
"The officials_team must be a Team.")
self.request.response.addNotification(
"Successfully changed the officials team to %s" %
officials_team.browsername)
else:
self.request.response.addNotification(
"Successfully removed the officials team")
@property
def next_url(self):
return canonical_url(self.context)
}}}
The View to appoint the officials team is configured with a new files
names officials.zcml.
{{{
}}}
The work of displaying the project's icon is always done from the
context of the `Person` in the templates. The templates requires some
minor changes to the templates where `owner/fmt:link` is called to show
a person's icon in context of a project, in messages listing
members/drivers, etc. The TALES expression to show either the project
icon or the person icon will take the form of
`owner/fmt:link-from-project`.
{{{
# Sample Person's icon in the Ubuntu project.
owner/fmt:link-from-project
}}}
The `PersonFormatterAPI` class's traverse method will be revised to
call a new method named link_from_project. This method will retrieve
the project from the `ILaunchbag`.
{{{#!python
def traverse(self, name, furtherPath):
...
elif name == 'link-from-project':
return self.link_from_project()
...
def link_from_project(self):
"""Return a link to a person from the context of a project."""
launchbag= getUtility(ILaunchBag)
if launchbag.distribution is not None:
project = launchbag.distribution
elif launchbag.product is not None:
project = launchbag.product
elif launchbag.project is not None:
project = launchbag.project
else:
# link-from-project was from a template used by a non-project.
project = None
person = self._context
url = canonical_url(person)
image_html = ObjectImageDisplayAPI(person, project).icon()
return '%s %s' % (
url, image_html, person.browsername)
}}}
The default_icon_resource method of `ObjectImageDisplayAPI` will accept
the project as a keyword parameter. It tests if the context
(a Person) is a member of the project's officials. When True,
it returns the path to the project's icon.
{{{#!python
def default_icon_resource(self, context, project=None):
if (IProduct.providedBy(context)
or (project is not None
and project.isOfficial(context))):
return '/@@/product'
elif (IProject.providedBy(context)
or (project is not None
and project.isOfficial(context))):
return '/@@/project'
elif IPerson.providedBy(context):
if context.isTeam():
return '/@@/team'
else:
if context.is_valid_person:
return '/@@/person'
else:
return '/@@/person-inactive'
elif IDistribution.providedBy(context):
return '/@@/distribution'
elif ISprint.providedBy(context):
return '/@@/meeting'
return '/@@/nyet-icon'
}}}
== Migration ==
None.
== Unresolved Issue and future developments ==
=== General issues ===
== Reject use cases ==
'''Official members always wear their project badges'''
This story illustrates how project badges improve inter-project communication.
Users can see who represents which projects in a discussion.
''Actors''
* Will Hackit, an official member of Ubuntu.
* Alton Star, an an official member of GTK and GNOME.
* Tom Smartie, a person with an idea.
1. Tom has an idea to improve Ubuntu so he creates a bug in Launchpad for it.
2. Will Hackit likes Tom's idea. He posts a comment with some suggestions about how to implement the idea.
3. Alton Star visits the same bug page. He also like the idea, but does not like Will's suggestion. Alton can see that Will is representing the Ubuntu project. Alton posts a counter suggestion that implementation happen in the GNOME project.
4. Will returns to the bug page. He reads Alton's suggestions, and can see both the GTK and GNOME project badges next to Alton's name.
5. Will adds a comment to the bug asking Alton to clarify his suggestion.
6. Alton returns to the page to reply to Will.
7. Will reads Alton's reply, and agrees with Alton that GNOME should implement the feature.
'''Responsibilities for Official members'''
By adding the concept of official members to projects, Launchpad
applications have enough information to permit some contributors to have
semi-admin responsibilities. The specific features that we implement will be
driven by the use cases we select for this spec. Once we can ask the
question if a person is in the project's official crowd, we can add several
iterations of work to add application specific responsibilities. eg.
* Official answer contacts may change the status of a question.
* Official bug and answer contacts may hide in appropriate messages.
This spec addresses the following bugs:
* [[https://bugs.launchpad.net/malone/+bug/82642|Allow admins to remove support contacts]]
* [[https://bugs.launchpad.net/launchpad-answers/+bug/85358|Allow hiding inappropriate comment]]
'''Official answer contacts can change Question status'''
This story is specifically about question status, but other Launchpad
applications can apply this example to the status of their entities.
''Actors''
* Joe Average: A person with a problem.
* Ken Doit: An Ubuntu answer contact.
* Will Hackit: An official answer contact for the Ubuntu project.
1. Joe visits the Ubuntu Answers page, and creates a new question.
2. Ken is notified of the question, he visits the question and adds his answer.
3. Joe is notified of the answer, he visits the question page and reads the answer.
4. Joe tries the answer and finds that is does solve his problem.
5. Joe return's the to question page and marks Ken's answer as the answer to the question, setting the state to answered.
6. Joe next adds a message to the question to thank Ken, but accidentally sets the question state back to open.
7. Ken is somewhat frustrated that he does not get credit for his answer because the question is in the wrong state. He explains the issue to Will on IRC.
8. Will visits the question page, chooses Change status from the Actions portlet, and sets the status back to answered.
'''Official bug contacts can hide inappropriate messages'''
This story is specifically about bug messages, but it also applies to
question messages. We should implement the feature for both applications.
''Actors''
* Nigel Wanker: A maker of link spam.
* Joe Average: A person with a problem.
* Will Hackit: An official bug contact for the Ubuntu project.
1. Joe visits the Ubuntu Bugs page and creates a new bug.
2. Will receives an email notification. He chooses to do nothing at this time.
3. Nigel visits the Ubuntu Bugs page, and chooses Joe's bug from the 'Recently reported' bugs list.
4. Nigel adds a comment that has a dozen links to porn sites.
5. Will receives an email notification, and he reads the comment that links to make porn pages. Will follows the link in the email to the bug.
6. Will locates Nigel's comment. There is a link in the header of the comment to 'Remove this comment'. Will can see it because he is an official bug contact. Will uses the link.
7. The page reloads. Will reads a notification that Nigel's comment was removed.
== Comments ==
* Francis suggests that we drop the use cases that offer specific responsibilities from this spec. <
> ''Done''