TrustedProjectMembers

Not logged in - Log In / Register

TrustedProjectMembers

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.

   1 class IHasOfficials(Interface):
   2     """An object that has officials.
   3 
   4     Officials represent their project in messages. They are granted
   5     additional responsibilities by Launchpad applications.
   6     """
   7 
   8     officials = Attribute(_("A crowd of officials"))
   9 
  10     def isAnOfficial(person):
  11         """Return True when person is an official, otherwise False.
  12 
  13         A person is an official when he is the project owner, driver, a
  14         member of the drivers team, or a member of the officials team.
  15         """
  16 
  17 class IHasAppointedOfficial(Interface):
  18     """An object that has an appointed official.
  19     
  20     An official is a team. It cannot have Teams as members.
  21     """
  22 
  23     officials_team = Choice(
  24         title=_("Officials team"),
  25         description=_("The team that projects' officials belong too."),
  26         required=False,
  27         vocabulary='ValidTeam')

IDistribution, IProduct, and IProject will inherit IHasOfficials and IHasAppointedOfficial.

   1 class IDistribution(IHasAppointedDriver, IHasDrivers, IHasOwner,
   2                     IHasOfficials, IHasAppointedOfficial, IBugTarget,
   3                     ISpecificationTarget, IHasSecurityContact,
   4                     IKarmaContext, IHasMentoringOffers, IHasSprints,
   5                     IHasTranslationGroup):
   6     ...

   1 class IProduct(IHasAppointedDriver, IHasDrivers, IHasOwner,
   2                IHasOfficials, IHasAppointedOfficial, IBugTarget,
   3                ISpecificationTarget, IHasSecurityContact, IKarmaContext,
   4                IHasSprints, IHasMentoringOffers, IHasLogo, IHasMugshot,
   5                IHasIcon, IHasBranchVisibilityPolicy, IHasTranslationGroup):
   6     ...

   1 class IProject(IHasAppointedDriver, IHasOwner, IHasOfficials,
   2                IHasAppointedOfficial, IBugTarget, IHasSpecifications,
   3                IKarmaContext, IHasSprints, IHasMentoringOffers, IHasIcon,
   4                IHasLogo, IHasMugshot, IHasBranchVisibilityPolicy,
   5                IHasTranslationGroup)
   6     ...

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?'.

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.

   1 class HasOfficialsMixin:
   2     """A mixin class that implements IHasOfficials and IHasAppointedOfficial.
   3     
   4     This class provides the the properties and methods to manage officials.
   5     """
   6 
   7     officials_team = ForeignKey(
   8         foreignKey="Person", dbName="officials_team",
   9         notNull=False, default=None)
  10 
  11     ...
  12 
  13     @property
  14     def officials(self):
  15         """See `IHasOfficials`."""
  16         return (
  17             ICrowd(self.owner) + ICrowd(self.driver) + ICrowd(officials_team))
  18 
  19     def isAnOfficial(self, person):
  20         """See `IHasOfficials`."""
  21         if person.isTeam():
  22             return False
  23         return person in self.officials
  24 
  25     ...

   1 class Distribution(SQLBase, BugTargetBase, HasOfficialsMixin,
   2                    HasSpecificationsMixin, HasSprintsMixin, KarmaContextMixin,
   3                    QuestionTargetMixin):
   4     ...

class Product(SQLBase, BugTargetBase, HasOfficialsMixin,
              HasSpecificationsMixin, HasSprintsMixin, KarmaContextMixin,
              BranchVisibilityPolicyMixin, QuestionTargetMixin):
    ...

   1 class Project(SQLBase, BugTargetBase, HasOfficialsMixin,
   2               HasSpecificationsMixin, HasSprintsMixin, KarmaContextMixin,
   3               BranchVisibilityPolicyMixin):
   4 
   5     ...

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.

   1 class DistributionOverviewMenu(ApplicationMenu):
   2     ...
   3     
   4     links = ['edit', 'branding', 'driver', 'officials_team', ...]
   5 
   6     ...
   7 
   8     @enabled_with_permission('launchpad.Edit')
   9     def officials_team(self):
  10         text = 'Appoint the official team'
  11         summary = 'Persons who are trusted to represent the project.'
  12         return Link('+officials_team', text, summary, icon='edit')
  13 
  14     ...
  15 
  16 
  17 class ProductOverviewMenu(ApplicationMenu):
  18     ...
  19     
  20     links = ['edit', 'branding', 'driver', 'officials_team', ...]
  21 
  22     ...
  23 
  24     @enabled_with_permission('launchpad.Edit')
  25     def officials_team(self):
  26         text = 'Appoint the official team'
  27         summary = 'Persons who are trusted to represent the project.'
  28         return Link('+officials_team', text, summary, icon='edit')
  29 
  30     ...
  31 
  32 
  33 class ProjectOverviewMenu(ApplicationMenu):
  34     ...
  35     
  36     links = ['edit', 'branding', 'driver', 'officials_team', ...]
  37 
  38     ...
  39 
  40     @enabled_with_permission('launchpad.Edit')
  41     def officials_team(self):
  42         text = 'Appoint the official team'
  43         summary = 'Persons who are trusted to represent the project.'
  44         return Link('+officials_team', text, summary, icon='edit')
  45 
  46     ...

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.

   1 class AppointOfficialsView(LaunchpadEditFormView):
   2     """Browser view for appointing the officials team to an object."""
   3 
   4     field_names = ['officials']
   5 
   6     @property
   7     def schema(self):
   8         """Return the schema that is the most specific extension of
   9         IHasAppointedDriver
  10         """
  11         assert IHasAppointedOfficials.providedBy(self.context), (
  12             "context should provide IHasAppointedOfficials.")
  13         for interface in providedBy(self.context):
  14             if interface.isOrExtends(IHasAppointedOfficials):
  15                 # XXX sinzui 2007-07-22 bug=84940: 
  16                 # removeSecurityProxy() is a needed because formlib
  17                 # does not use the security-aware isinstance() wrapper.
  18                 return removeSecurityProxy(interface)
  19 
  20     @action('Change', name='change')
  21     def change_action(self, action, data):
  22         officials_team = data['officials_team']
  23         self.updateContextFromData(data)
  24         if officials_team is not None:
  25             assert driver.isTeam(), (
  26                 "The officials_team must be a Team.")
  27             self.request.response.addNotification(
  28                 "Successfully changed the officials team to %s" %
  29                 officials_team.browsername)
  30         else:
  31             self.request.response.addNotification(
  32                 "Successfully removed the officials team")
  33 
  34     @property
  35     def next_url(self):
  36         return canonical_url(self.context)

The View to appoint the officials team is configured with a new files names officials.zcml.

<configure
  xmlns="http://namespaces.zope.org/zope"
  xmlns:browser="http://namespaces.zope.org/browser"
  xmlns:i18n="http://namespaces.zope.org/i18n"
  i18n_domain="launchpad">
  <browser:page
    name="+officials_team"
    for="canonical.launchpad.interfaces.IHasAppointedOfficials"
    class="canonical.launchpad.browser.officials.AppointOfficialsView"
    facet="overview"
    permission="launchpad.Edit"
    template="../templates/object-officials.pt"
    />
</configure>

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.

   1     def traverse(self, name, furtherPath):
   2         ...
   3         elif name == 'link-from-project':
   4             return self.link_from_project()
   5         ...
   6     
   7     def link_from_project(self):
   8         """Return a link to a person from the context of a project."""
   9         launchbag= getUtility(ILaunchBag)
  10         if launchbag.distribution is not None:
  11             project = launchbag.distribution
  12         elif launchbag.product is not None:
  13             project = launchbag.product
  14         elif launchbag.project is not None:
  15             project = launchbag.project
  16         else:
  17             # link-from-project was from a template used by a non-project.
  18             project = None 
  19         person = self._context
  20         url = canonical_url(person)
  21         image_html = ObjectImageDisplayAPI(person, project).icon()
  22         return '<a href="%s">%s&nbsp;%s</a>' % (
  23             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.

   1     def default_icon_resource(self, context, project=None):
   2         if (IProduct.providedBy(context)
   3             or (project is not None 
   4             and project.isOfficial(context))):
   5             return '/@@/product'
   6         elif (IProject.providedBy(context)
   7             or (project is not None 
   8             and project.isOfficial(context))):
   9             return '/@@/project'
  10         elif IPerson.providedBy(context):
  11             if context.isTeam():
  12                 return '/@@/team'
  13             else:
  14                 if context.is_valid_person:
  15                     return '/@@/person'
  16                 else:
  17                     return '/@@/person-inactive'
  18         elif IDistribution.providedBy(context):
  19             return '/@@/distribution'
  20         elif ISprint.providedBy(context):
  21             return '/@@/meeting'
  22         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

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.

This spec addresses the following bugs:

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

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

Comments

TrustedProjectMembers (last edited 2009-05-26 10:55:01 by sinzui)