Use people.json artifact (#162)
This commit is contained in:
Родитель
c4f2ad88f1
Коммит
858b213145
|
@ -1,25 +0,0 @@
|
|||
import json
|
||||
import argparse
|
||||
|
||||
import yaml
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Process Phonebook file and prepares it for TaskCluster Secret.'
|
||||
)
|
||||
parser.add_argument('--path', type=str, dest='phonebook_file', action='store',
|
||||
help='Path to Phonebook file', required='true')
|
||||
args = parser.parse_args()
|
||||
|
||||
with open(args.phonebook_file) as jsonfile:
|
||||
parsed = json.load(jsonfile)
|
||||
|
||||
reducedOrgData = {
|
||||
'employees': []
|
||||
}
|
||||
for entry in parsed:
|
||||
reducedOrgData['employees'].append({ key: entry.get(key) for key in ['cn', 'mail', 'bugzillaEmail', 'manager'] })
|
||||
|
||||
with open('orgData.yml', 'w') as fh:
|
||||
yaml.safe_dump(reducedOrgData, fh)
|
||||
|
||||
print('The file orgData.yml has been generated. You can now upload it to Taskcluster Secrets if you wish to')
|
|
@ -17,13 +17,13 @@ const styles = theme => ({
|
|||
});
|
||||
|
||||
const Header = ({
|
||||
classes, selectedTabIndex, handleTabChange, ldapEmail,
|
||||
classes, selectedTabIndex, handleTabChange, userId,
|
||||
}) => (
|
||||
<AppBar position="static">
|
||||
<Toolbar className={classes.styledToolbar}>
|
||||
<Tabs value={selectedTabIndex} onChange={handleTabChange}>
|
||||
<Tab label="Reportees" component={NavLink} to={`reportees?ldapEmail=${ldapEmail}`} />
|
||||
<Tab label="Components" component={NavLink} to={`components?ldapEmail=${ldapEmail}`} />
|
||||
<Tab label="Reportees" component={NavLink} to={`reportees?userId=${userId}`} />
|
||||
<Tab label="Components" component={NavLink} to={`components?userId=${userId}`} />
|
||||
</Tabs>
|
||||
<CredentialsMenu />
|
||||
</Toolbar>
|
||||
|
@ -34,7 +34,7 @@ Header.propTypes = {
|
|||
classes: PropTypes.shape({}).isRequired,
|
||||
selectedTabIndex: PropTypes.number.isRequired,
|
||||
handleTabChange: PropTypes.func.isRequired,
|
||||
ldapEmail: PropTypes.string.isRequired,
|
||||
userId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default withStyles(styles)(Header);
|
||||
|
|
|
@ -12,11 +12,11 @@ const styles = {
|
|||
|
||||
class Reportees extends React.PureComponent {
|
||||
getMergedProps() {
|
||||
const { metrics, partialOrg, ldapEmail } = this.props;
|
||||
const { metrics, partialOrg, userId } = this.props;
|
||||
|
||||
// filter out the manager
|
||||
const reportees = Object.values(partialOrg)
|
||||
.filter(({ mail }) => mail !== ldapEmail);
|
||||
.filter(({ mail }) => mail !== userId);
|
||||
|
||||
// add metrics
|
||||
const reporteesWithMetrics = reportees.map(reportee => ({
|
||||
|
@ -24,7 +24,7 @@ class Reportees extends React.PureComponent {
|
|||
...metrics[reportee.bugzillaEmail],
|
||||
}));
|
||||
// Sort dataset in ascending order and return
|
||||
return reporteesWithMetrics.sort((a, b) => a.cn.localeCompare(b.cn));
|
||||
return reporteesWithMetrics.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
|
||||
// Custom styles to override default MUI theme
|
||||
|
@ -63,7 +63,7 @@ class Reportees extends React.PureComponent {
|
|||
// Form Table column headers using metricsArray
|
||||
// Add Full name directly into columns Heading array
|
||||
const firstColumn = {
|
||||
name: 'cn',
|
||||
name: 'name',
|
||||
label: 'Full Name',
|
||||
};
|
||||
|
||||
|
@ -110,14 +110,14 @@ class Reportees extends React.PureComponent {
|
|||
}
|
||||
Reportees.propTypes = {
|
||||
classes: PropTypes.shape({}).isRequired,
|
||||
ldapEmail: PropTypes.string,
|
||||
userId: PropTypes.string,
|
||||
partialOrg: PropTypes.shape({}).isRequired,
|
||||
metrics: PropTypes.shape({}),
|
||||
};
|
||||
|
||||
Reportees.defaultProps = {
|
||||
metrics: {},
|
||||
ldapEmail: '',
|
||||
userId: '',
|
||||
};
|
||||
|
||||
export default withStyles(styles)(Reportees);
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { Secrets, Index } from 'taskcluster-client-web';
|
||||
import { Index } from 'taskcluster-client-web';
|
||||
import { TASKCLUSTER_ROOT_URL } from '../../config';
|
||||
|
||||
const USER_ID_REGEX = /mozilla-auth0\/([\w-|]+)\/bugzilla-dashboard-([\w-]+)/;
|
||||
|
||||
/**
|
||||
* An object representing a user session. Tools supports a variety of login methods,
|
||||
* so this combines them all in a single representation.
|
||||
|
@ -56,6 +58,16 @@ export default class UserSession {
|
|||
);
|
||||
}
|
||||
|
||||
get userId() {
|
||||
// Find the user ID in Taskcluster credentials
|
||||
const match = USER_ID_REGEX.exec(this.credentials.clientId);
|
||||
if (match === null) {
|
||||
return this.credentials.clientId;
|
||||
}
|
||||
|
||||
return match[1];
|
||||
}
|
||||
|
||||
// get the args used to create a new client object
|
||||
get clientArgs() {
|
||||
return { credentials: this.credentials };
|
||||
|
@ -76,11 +88,6 @@ export default class UserSession {
|
|||
return JSON.stringify({ ...this, credentialAgent: undefined });
|
||||
}
|
||||
|
||||
getTaskClusterSecretsClient = () => new Secrets({
|
||||
...this.clientArgs,
|
||||
rootUrl: TASKCLUSTER_ROOT_URL,
|
||||
});
|
||||
|
||||
getTaskClusterIndexClient = () => new Index({
|
||||
...this.clientArgs,
|
||||
rootUrl: TASKCLUSTER_ROOT_URL,
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
const PRODUCTION = process.env.NODE_ENV === 'production';
|
||||
export const TASKCLUSTER_ROOT_URL = PRODUCTION ? 'https://firefox-ci-tc.services.mozilla.com' : 'https://stage.taskcluster.nonprod.cloudops.mozgcp.net';
|
||||
|
||||
const channel = PRODUCTION ? 'production' : 'testing';
|
||||
|
||||
const config = {
|
||||
artifactRoute: 'project.relman.production.bugzilla-dashboard.latest',
|
||||
taskclusterSecrets: {
|
||||
orgData: 'project/relman/bugzilla-dashboard/org',
|
||||
},
|
||||
artifactRoute: `project.relman.${channel}.bugzilla-dashboard.latest`,
|
||||
OAuth2Options: {
|
||||
clientId: PRODUCTION ? 'bugzilla-dashboard-production' : 'bugzilla-dashboard-localdev',
|
||||
scopes: ['queue:get-artifact:project/relman/bugzilla-dashboard/*'],
|
||||
|
@ -19,6 +18,7 @@ const config = {
|
|||
},
|
||||
productComponentMetrics: 'project/relman/bugzilla-dashboard/product_component_data.json.gz',
|
||||
reporteesMetrics: 'project/relman/bugzilla-dashboard/reportee_data.json.gz',
|
||||
peopleTree: 'project/relman/bugzilla-dashboard/people.json.gz',
|
||||
};
|
||||
|
||||
export const REPORTEES_CONFIG = {
|
||||
|
|
|
@ -20,6 +20,10 @@ async function loadArtifact(userSession, route, artifactName) {
|
|||
);
|
||||
const resp = await fetch(url);
|
||||
|
||||
if (resp.status !== 200) {
|
||||
throw new Error(`Failed to download artifact ${artifactName}`);
|
||||
}
|
||||
|
||||
return resp.arrayBuffer();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,68 +1,31 @@
|
|||
import pako from 'pako';
|
||||
import config from '../config';
|
||||
import loadArtifact from './artifacts';
|
||||
|
||||
const buildOrgChartData = (people) => {
|
||||
const org = {};
|
||||
people.forEach((person) => {
|
||||
const { mail } = person;
|
||||
if (!org[mail]) {
|
||||
org[mail] = person;
|
||||
org[mail].reportees = [];
|
||||
} else {
|
||||
org[mail] = {
|
||||
...person,
|
||||
reportees: org[mail].reportees,
|
||||
};
|
||||
const findReportees = (org, parentId) => {
|
||||
// Find all direct reportees of specified manager
|
||||
const managerFilter = (acc, key) => {
|
||||
if (org[key].manager !== parentId) {
|
||||
return acc;
|
||||
}
|
||||
const { manager } = person;
|
||||
if (manager) {
|
||||
const managerLDAPemail = manager.dn.split('mail=')[1].split(',o=')[0];
|
||||
if (org[managerLDAPemail]) {
|
||||
org[managerLDAPemail].reportees.push(mail);
|
||||
} else {
|
||||
org[managerLDAPemail] = {
|
||||
reportees: [mail],
|
||||
};
|
||||
}
|
||||
}
|
||||
if (!org[mail].bugzillaEmail) {
|
||||
org[mail].bugzillaEmail = mail;
|
||||
}
|
||||
});
|
||||
return org;
|
||||
};
|
||||
|
||||
const findReportees = (completeOrg, email) => {
|
||||
let allReportees = {};
|
||||
// if non-LDAP user, replace user email by the last email in list. Last email
|
||||
const allEmails = Object.keys(completeOrg);
|
||||
const checkedEmail = (email in completeOrg) ? email : allEmails[allEmails.length - 1];
|
||||
|
||||
allReportees[email] = completeOrg[checkedEmail];
|
||||
const { reportees } = completeOrg[checkedEmail];
|
||||
|
||||
return { ...acc, [key]: org[key] };
|
||||
};
|
||||
let reportees = Object.keys(org).reduce(managerFilter, {});
|
||||
|
||||
// Add subordinates reportees too
|
||||
if (reportees.length !== 0) {
|
||||
reportees.forEach((reporteeEmail) => {
|
||||
const partialOrg = findReportees(completeOrg, reporteeEmail);
|
||||
allReportees = { ...allReportees, ...partialOrg };
|
||||
Object.keys(reportees).forEach((rId) => {
|
||||
const subordinates = findReportees(org, rId);
|
||||
reportees = { ...reportees, ...subordinates };
|
||||
});
|
||||
}
|
||||
return allReportees;
|
||||
return reportees;
|
||||
};
|
||||
|
||||
const getAllReportees = async (userSession, ldapEmail) => {
|
||||
let people;
|
||||
if (process.env.ALTERNATIVE_AUTH) {
|
||||
// if non-LDAP user, get fake data
|
||||
people = await (await fetch('people.json')).json();
|
||||
} else {
|
||||
// LDAP user, retrieve data from the taskcluster secret
|
||||
const secretsClient = userSession.getTaskClusterSecretsClient();
|
||||
const { secret } = await await secretsClient.get(config.taskclusterSecrets.orgData);
|
||||
people = secret.employees;
|
||||
}
|
||||
const completeOrg = await buildOrgChartData(people);
|
||||
return findReportees(completeOrg, ldapEmail);
|
||||
const getAllReportees = async (userSession, userId) => {
|
||||
const peopleGZ = await loadArtifact(userSession, config.artifactRoute, config.peopleTree);
|
||||
const people = JSON.parse(pako.inflate(peopleGZ, { to: 'string' }));
|
||||
return findReportees(people, userId || userSession.userId);
|
||||
};
|
||||
|
||||
export default getAllReportees;
|
||||
|
|
|
@ -28,7 +28,7 @@ const DEFAULT_STATE = {
|
|||
selectedTabIndex: 0,
|
||||
reporteesMetrics: {},
|
||||
componentDetails: undefined,
|
||||
ldapEmail: '',
|
||||
userId: '',
|
||||
};
|
||||
|
||||
const PATHNAME_TO_TAB_INDEX = {
|
||||
|
@ -79,8 +79,8 @@ class MainContainer extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
async getReportees(userSession, ldapEmail) {
|
||||
const partialOrg = await getAllReportees(userSession, ldapEmail);
|
||||
async getReportees(userSession, userId) {
|
||||
const partialOrg = await getAllReportees(userSession, userId);
|
||||
this.setState({ partialOrg });
|
||||
return partialOrg;
|
||||
}
|
||||
|
@ -103,9 +103,9 @@ class MainContainer extends Component {
|
|||
// We show the spinner after having signed in
|
||||
this.setState({ doneLoading: false });
|
||||
const { location } = this.props;
|
||||
const ldapEmail = new URLSearchParams(location.search).get('ldapEmail') || (userSession && userSession.email);
|
||||
this.setState({ ldapEmail });
|
||||
this.retrieveData(userSession, ldapEmail);
|
||||
const userId = new URLSearchParams(location.search).get('userId') || userSession.userId;
|
||||
this.setState({ userId });
|
||||
this.retrieveData(userSession, userId);
|
||||
} else {
|
||||
this.setState(DEFAULT_STATE);
|
||||
}
|
||||
|
@ -189,10 +189,10 @@ class MainContainer extends Component {
|
|||
);
|
||||
}
|
||||
|
||||
async retrieveData(userSession, ldapEmail) {
|
||||
async retrieveData(userSession, userId) {
|
||||
const [bzOwners, partialOrg] = await Promise.all([
|
||||
getBugzillaOwners(),
|
||||
this.getReportees(userSession, ldapEmail),
|
||||
this.getReportees(userSession, userId),
|
||||
]);
|
||||
// Fetch this data first since it's the landing tab
|
||||
await this.reporteesMetrics(userSession, partialOrg);
|
||||
|
@ -262,7 +262,7 @@ class MainContainer extends Component {
|
|||
doneLoading,
|
||||
componentDetails,
|
||||
bugzillaComponents,
|
||||
ldapEmail,
|
||||
userId,
|
||||
partialOrg,
|
||||
teamComponents,
|
||||
selectedTabIndex,
|
||||
|
@ -275,9 +275,9 @@ class MainContainer extends Component {
|
|||
return (
|
||||
<div>
|
||||
<Header
|
||||
userId={userId}
|
||||
selectedTabIndex={selectedTabIndex}
|
||||
handleTabChange={this.handleNavigateAndClear}
|
||||
ldapEmail={ldapEmail}
|
||||
/>
|
||||
<div className={classes.content}>
|
||||
{!userSession && <h3>Please sign in</h3>}
|
||||
|
@ -295,7 +295,7 @@ class MainContainer extends Component {
|
|||
<PropsRoute
|
||||
path="/reportees"
|
||||
component={Reportees}
|
||||
ldapEmail={ldapEmail}
|
||||
userId={userId}
|
||||
partialOrg={partialOrg}
|
||||
metrics={reporteesMetrics}
|
||||
/>
|
||||
|
|
|
@ -10,7 +10,7 @@ it('renders the reportees tab', () => {
|
|||
<Header
|
||||
selectedTabIndex={0}
|
||||
handleTabChange={() => null}
|
||||
ldapEmail="fbar@mozilla.com"
|
||||
userId="fbar@mozilla.com"
|
||||
/>
|
||||
</Router>
|
||||
))
|
||||
|
|
|
@ -7,7 +7,7 @@ it('renders Someone with no reportees', () => {
|
|||
const tree = renderer
|
||||
.create((
|
||||
<Reportees
|
||||
ldapEmail="someone@mozilla.com"
|
||||
userId="someone@mozilla.com"
|
||||
partialOrg={partialOrg}
|
||||
/>
|
||||
))
|
||||
|
@ -19,7 +19,7 @@ it('renders Manager who has reportees', () => {
|
|||
const tree = renderer
|
||||
.create((
|
||||
<Reportees
|
||||
ldapEmail="manager@mozilla.com"
|
||||
userId="manager@mozilla.com"
|
||||
partialOrg={partialOrg}
|
||||
/>
|
||||
))
|
||||
|
@ -31,7 +31,7 @@ it('renders Manager who has reportees & metrics', () => {
|
|||
const tree = renderer
|
||||
.create((
|
||||
<Reportees
|
||||
ldapEmail="manager@mozilla.com"
|
||||
userId="manager@mozilla.com"
|
||||
partialOrg={partialOrg}
|
||||
metrics={{
|
||||
'someone@mozilla.com': {
|
||||
|
|
|
@ -30,7 +30,7 @@ exports[`renders the reportees tab 1`] = `
|
|||
aria-current={null}
|
||||
aria-selected={true}
|
||||
className="MuiButtonBase-root-63 MuiTab-root-51 MuiTab-textColorInherit-53 MuiTab-selected-56"
|
||||
href="/reportees?ldapEmail=fbar@mozilla.com"
|
||||
href="/reportees?userId=fbar@mozilla.com"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onContextMenu={[Function]}
|
||||
|
@ -67,7 +67,7 @@ exports[`renders the reportees tab 1`] = `
|
|||
aria-current={null}
|
||||
aria-selected={false}
|
||||
className="MuiButtonBase-root-63 MuiTab-root-51 MuiTab-textColorInherit-53"
|
||||
href="/components?ldapEmail=fbar@mozilla.com"
|
||||
href="/components?userId=fbar@mozilla.com"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onContextMenu={[Function]}
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
const partialOrg = {
|
||||
'someone@mozilla.com': {
|
||||
bugzillaEmail: 'someone@mozilla.com',
|
||||
cn: 'Someone',
|
||||
name: 'Someone',
|
||||
mail: 'someone@mozilla.com',
|
||||
manager: {
|
||||
dn: 'mail=manager@mozilla.com,o=com,dc=mozilla',
|
||||
},
|
||||
manager: 'manager@mozilla.com',
|
||||
},
|
||||
'manager@mozilla.com': {
|
||||
bugzillaEmail: 'someone@mozilla.com',
|
||||
cn: 'Manager',
|
||||
name: 'Manager',
|
||||
mail: 'manager@mozilla.com',
|
||||
manager: null,
|
||||
},
|
||||
|
|
Загрузка…
Ссылка в новой задаче