|  | // Copyright (C) 2020 The Android Open Source Project | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use size file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  |  | 
|  | import * as m from 'mithril'; | 
|  |  | 
|  | import {Actions} from '../common/actions'; | 
|  |  | 
|  | import {Flow, globals} from './globals'; | 
|  | import {BLANK_CHECKBOX, CHECKBOX} from './icons'; | 
|  | import {Panel, PanelSize} from './panel'; | 
|  |  | 
|  | export const ALL_CATEGORIES = '_all_'; | 
|  |  | 
|  | export function getFlowCategories(flow: Flow): string[] { | 
|  | const categories: string[] = []; | 
|  | // v1 flows have their own categories | 
|  | if (flow.category) { | 
|  | categories.push(...flow.category.split(',')); | 
|  | return categories; | 
|  | } | 
|  | const beginCats = flow.begin.sliceCategory.split(','); | 
|  | const endCats = flow.end.sliceCategory.split(','); | 
|  | categories.push(...new Set([...beginCats, ...endCats])); | 
|  | return categories; | 
|  | } | 
|  |  | 
|  | export class FlowEventsPanel extends Panel { | 
|  | view() { | 
|  | const selection = globals.state.currentSelection; | 
|  | if (!selection || selection.kind !== 'CHROME_SLICE') { | 
|  | return; | 
|  | } | 
|  |  | 
|  | const flowClickHandler = (sliceId: number, trackId: number) => { | 
|  | const uiTrackId = globals.state.uiTrackIdByTraceTrackId[trackId]; | 
|  | if (uiTrackId) { | 
|  | globals.makeSelection( | 
|  | Actions.selectChromeSlice( | 
|  | {id: sliceId, trackId: uiTrackId, table: 'slice'}), | 
|  | 'bound_flows'); | 
|  | } | 
|  | }; | 
|  |  | 
|  | // Can happen only for flow events version 1 | 
|  | const haveCategories = | 
|  | globals.connectedFlows.filter(flow => flow.category).length > 0; | 
|  |  | 
|  | const columns = [ | 
|  | m('th', 'Direction'), | 
|  | m('th', 'Connected Slice ID'), | 
|  | m('th', 'Connected Slice Name') | 
|  | ]; | 
|  |  | 
|  | if (haveCategories) { | 
|  | columns.push(m('th', 'Flow Category')); | 
|  | columns.push(m('th', 'Flow Name')); | 
|  | } | 
|  |  | 
|  | const rows = [m('tr', columns)]; | 
|  |  | 
|  | // Fill the table with all the directly connected flow events | 
|  | globals.connectedFlows.forEach(flow => { | 
|  | if (selection.id !== flow.begin.sliceId && | 
|  | selection.id !== flow.end.sliceId) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | const outgoing = selection.id === flow.begin.sliceId; | 
|  | const otherEnd = (outgoing ? flow.end : flow.begin); | 
|  |  | 
|  | const args = { | 
|  | onclick: () => flowClickHandler(otherEnd.sliceId, otherEnd.trackId), | 
|  | onmousemove: () => globals.dispatch( | 
|  | Actions.setHighlightedSliceId({sliceId: otherEnd.sliceId})), | 
|  | onmouseleave: () => | 
|  | globals.dispatch(Actions.setHighlightedSliceId({sliceId: -1})), | 
|  | }; | 
|  |  | 
|  | const data = [ | 
|  | m('td.flow-link', args, outgoing ? 'Outgoing' : 'Incoming'), | 
|  | m('td.flow-link', args, otherEnd.sliceId.toString()), | 
|  | m('td.flow-link', args, otherEnd.sliceName) | 
|  | ]; | 
|  |  | 
|  | if (haveCategories) { | 
|  | data.push(m('td.flow-info', flow.category || '-')); | 
|  | data.push(m('td.flow-info', flow.name || '-')); | 
|  | } | 
|  |  | 
|  | rows.push(m('tr', data)); | 
|  | }); | 
|  |  | 
|  | return m('.details-panel', [ | 
|  | m('.details-panel-heading', m('h2', `Flow events`)), | 
|  | m('.flow-events-table', m('table.half-width', rows)) | 
|  | ]); | 
|  | } | 
|  |  | 
|  | renderCanvas(_ctx: CanvasRenderingContext2D, _size: PanelSize) {} | 
|  | } | 
|  |  | 
|  | export class FlowEventsAreaSelectedPanel extends Panel { | 
|  | view() { | 
|  | const selection = globals.state.currentSelection; | 
|  | if (!selection || selection.kind !== 'AREA') { | 
|  | return; | 
|  | } | 
|  |  | 
|  | const columns = [ | 
|  | m('th', 'Flow Category'), | 
|  | m('th', 'Number of flows'), | 
|  | m('th', | 
|  | 'Show', | 
|  | m('a.warning', | 
|  | m('i.material-icons', 'warning'), | 
|  | m('.tooltip', | 
|  | 'Showing a large number of flows may impact performance.'))) | 
|  | ]; | 
|  |  | 
|  | const rows = [m('tr', columns)]; | 
|  |  | 
|  | const categoryToFlowsNum = new Map<string, number>(); | 
|  |  | 
|  | globals.selectedFlows.forEach(flow => { | 
|  | const categories = getFlowCategories(flow); | 
|  | categories.forEach(cat => { | 
|  | if (!categoryToFlowsNum.has(cat)) { | 
|  | categoryToFlowsNum.set(cat, 0); | 
|  | } | 
|  | categoryToFlowsNum.set(cat, categoryToFlowsNum.get(cat)! + 1); | 
|  | }); | 
|  | }); | 
|  |  | 
|  | const allWasChecked = globals.visibleFlowCategories.get(ALL_CATEGORIES); | 
|  | rows.push(m('tr.sum', [ | 
|  | m('td.sum-data', 'All'), | 
|  | m('td.sum-data', globals.selectedFlows.length), | 
|  | m('td.sum-data', | 
|  | m('i.material-icons', | 
|  | { | 
|  | onclick: () => { | 
|  | if (allWasChecked) { | 
|  | globals.visibleFlowCategories.clear(); | 
|  | } else { | 
|  | categoryToFlowsNum.forEach((_, cat) => { | 
|  | globals.visibleFlowCategories.set(cat, true); | 
|  | }); | 
|  | } | 
|  | globals.visibleFlowCategories.set(ALL_CATEGORIES, !allWasChecked); | 
|  | globals.rafScheduler.scheduleFullRedraw(); | 
|  | }, | 
|  | }, | 
|  | allWasChecked ? CHECKBOX : BLANK_CHECKBOX)) | 
|  | ])); | 
|  |  | 
|  | categoryToFlowsNum.forEach((num, cat) => { | 
|  | const wasChecked = globals.visibleFlowCategories.get(cat) || | 
|  | globals.visibleFlowCategories.get(ALL_CATEGORIES); | 
|  | const data = [ | 
|  | m('td.flow-info', cat), | 
|  | m('td.flow-info', num), | 
|  | m('td.flow-info', | 
|  | m('i.material-icons', | 
|  | { | 
|  | onclick: () => { | 
|  | if (wasChecked) { | 
|  | globals.visibleFlowCategories.set(ALL_CATEGORIES, false); | 
|  | } | 
|  | globals.visibleFlowCategories.set(cat, !wasChecked); | 
|  | globals.rafScheduler.scheduleFullRedraw(); | 
|  | }, | 
|  | }, | 
|  | wasChecked ? CHECKBOX : BLANK_CHECKBOX)) | 
|  | ]; | 
|  | rows.push(m('tr', data)); | 
|  | }); | 
|  |  | 
|  | return m('.details-panel', [ | 
|  | m('.details-panel-heading', m('h2', `Selected flow events`)), | 
|  | m('.flow-events-table', m('table.half-width', rows)), | 
|  | ]); | 
|  | } | 
|  |  | 
|  | renderCanvas(_ctx: CanvasRenderingContext2D, _size: PanelSize) {} | 
|  | } |