This commit is contained in:
Bastien Abadie 2020-01-21 12:58:33 +01:00 коммит произвёл GitHub
Родитель c4f2ad88f1
Коммит 858b213145
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
12 изменённых файлов: 70 добавлений и 123 удалений

Просмотреть файл

@ -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,
},