Add databases plugin v0 (android) (#441)
Summary: Adds a plugin for listing the databases, tables and contents of those tables in an android app. Right now, works with sqlite, but it should be generic enough to work with other db types. ## Changelog Add initial version of android databases plugin Creating a PR, I may need to do some cleaning up, but this is to kick off that process. Pull Request resolved: https://github.com/facebook/flipper/pull/441 Reviewed By: danielbuechele Differential Revision: D15288831 Pulled By: jknoxville fbshipit-source-id: 6379ad60d640ea6b0a9473acc03dd6ea81a3a8d4
This commit is contained in:
committed by
Facebook Github Bot
parent
f20a781bca
commit
a630b50a8f
45
src/plugins/databases/ButtonNavigation.js
Normal file
45
src/plugins/databases/ButtonNavigation.js
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright 2018-present Facebook.
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
* @format
|
||||
*/
|
||||
import {Button, ButtonGroup, Glyph, colors} from 'flipper';
|
||||
|
||||
export default function ButtonNavigation(props: {|
|
||||
/** Back button is enabled */
|
||||
canGoBack: boolean,
|
||||
/** Forwards button is enabled */
|
||||
canGoForward: boolean,
|
||||
/** Callback when back button is clicked */
|
||||
onBack: () => void,
|
||||
/** Callback when forwards button is clicked */
|
||||
onForward: () => void,
|
||||
|}) {
|
||||
return (
|
||||
<ButtonGroup>
|
||||
<Button disabled={!props.canGoBack} onClick={props.onBack}>
|
||||
<Glyph
|
||||
name="chevron-left"
|
||||
size={16}
|
||||
color={
|
||||
props.canGoBack
|
||||
? colors.macOSTitleBarIconActive
|
||||
: colors.macOSTitleBarIconBlur
|
||||
}
|
||||
/>
|
||||
</Button>
|
||||
<Button disabled={!props.canGoForward} onClick={props.onForward}>
|
||||
<Glyph
|
||||
name="chevron-right"
|
||||
size={16}
|
||||
color={
|
||||
props.canGoForward
|
||||
? colors.macOSTitleBarIconActive
|
||||
: colors.macOSTitleBarIconBlur
|
||||
}
|
||||
/>
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
);
|
||||
}
|
||||
70
src/plugins/databases/ClientProtocol.js
Normal file
70
src/plugins/databases/ClientProtocol.js
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright 2018-present Facebook.
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {PluginClient} from '../../plugin';
|
||||
import type {Value} from '../../ui/components/table/TypeBasedValueRenderer';
|
||||
|
||||
type ClientCall<Params, Response> = Params => Promise<Response>;
|
||||
|
||||
type DatabaseListRequest = {};
|
||||
|
||||
type DatabaseListResponse = Array<{
|
||||
id: number,
|
||||
name: string,
|
||||
tables: Array<string>,
|
||||
}>;
|
||||
|
||||
type QueryTableRequest = {
|
||||
databaseId: number,
|
||||
table: string,
|
||||
order?: string,
|
||||
reverse: boolean,
|
||||
start: number,
|
||||
count: number,
|
||||
};
|
||||
|
||||
type QueryTableResponse = {
|
||||
columns: Array<string>,
|
||||
values: Array<Array<Value>>,
|
||||
start: number,
|
||||
count: number,
|
||||
total: number,
|
||||
};
|
||||
|
||||
type GetTableStructureRequest = {
|
||||
databaseId: number,
|
||||
table: string,
|
||||
};
|
||||
|
||||
type GetTableStructureResponse = {
|
||||
structureColumns: Array<string>,
|
||||
structureValues: Array<Array<Value>>,
|
||||
indexesColumns: Array<string>,
|
||||
indexesValues: Array<Array<Value>>,
|
||||
definition: string,
|
||||
};
|
||||
|
||||
export class DatabaseClient {
|
||||
client: PluginClient;
|
||||
|
||||
constructor(pluginClient: PluginClient) {
|
||||
this.client = pluginClient;
|
||||
}
|
||||
|
||||
getDatabases: ClientCall<
|
||||
DatabaseListRequest,
|
||||
DatabaseListResponse,
|
||||
> = params => this.client.call('databaseList', {});
|
||||
|
||||
getTableData: ClientCall<QueryTableRequest, QueryTableResponse> = params =>
|
||||
this.client.call('getTableData', params);
|
||||
|
||||
getTableStructure: ClientCall<
|
||||
GetTableStructureRequest,
|
||||
GetTableStructureResponse,
|
||||
> = params => this.client.call('getTableStructure', params);
|
||||
}
|
||||
744
src/plugins/databases/index.js
Normal file
744
src/plugins/databases/index.js
Normal file
@@ -0,0 +1,744 @@
|
||||
/**
|
||||
* Copyright 2018-present Facebook.
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {
|
||||
styled,
|
||||
Toolbar,
|
||||
Select,
|
||||
FlexColumn,
|
||||
FlexRow,
|
||||
ManagedTable,
|
||||
Text,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Input,
|
||||
} from 'flipper';
|
||||
import {Component} from 'react';
|
||||
import type {
|
||||
TableBodyRow,
|
||||
TableRowSortOrder,
|
||||
} from '../../ui/components/table/types';
|
||||
import {FlipperPlugin} from 'flipper';
|
||||
import {DatabaseClient} from './ClientProtocol';
|
||||
import {renderValue} from 'flipper';
|
||||
import type {Value} from 'flipper';
|
||||
import ButtonNavigation from './ButtonNavigation';
|
||||
import _ from 'lodash';
|
||||
|
||||
const PAGE_SIZE = 50;
|
||||
|
||||
const BoldSpan = styled('Span')({
|
||||
fontSize: 12,
|
||||
color: '#90949c',
|
||||
fontWeight: 'bold',
|
||||
textTransform: 'uppercase',
|
||||
});
|
||||
|
||||
type DatabasesPluginState = {|
|
||||
selectedDatabase: number,
|
||||
selectedDatabaseTable: ?string,
|
||||
pageRowNumber: number,
|
||||
databases: Array<DatabaseEntry>,
|
||||
viewMode: 'data' | 'structure',
|
||||
error: ?null,
|
||||
currentPage: ?Page,
|
||||
currentStructure: ?Structure,
|
||||
currentSort: ?TableRowSortOrder,
|
||||
|};
|
||||
|
||||
type Page = {
|
||||
databaseId: number,
|
||||
table: string,
|
||||
columns: Array<string>,
|
||||
rows: Array<TableBodyRow>,
|
||||
start: number,
|
||||
count: number,
|
||||
total: number,
|
||||
};
|
||||
|
||||
type Structure = {|
|
||||
databaseId: number,
|
||||
table: string,
|
||||
columns: Array<string>,
|
||||
rows: Array<TableBodyRow>,
|
||||
indexesColumns: Array<string>,
|
||||
indexesValues: Array<TableBodyRow>,
|
||||
|};
|
||||
|
||||
type Actions =
|
||||
| SelectDatabaseEvent
|
||||
| SelectDatabaseTableEvent
|
||||
| UpdateDatabasesEvent
|
||||
| UpdateViewModeEvent
|
||||
| UpdatePageEvent
|
||||
| UpdateStructureEvent
|
||||
| NextPageEvent
|
||||
| PreviousPageEvent
|
||||
| RefreshEvent
|
||||
| SortByChangedEvent
|
||||
| GoToRowEvent;
|
||||
|
||||
type DatabaseEntry = {
|
||||
id: number,
|
||||
name: string,
|
||||
tables: Array<string>,
|
||||
};
|
||||
|
||||
type UpdateDatabasesEvent = {|
|
||||
databases: Array<{name: string, id: number, tables: Array<string>}>,
|
||||
type: 'UpdateDatabases',
|
||||
|};
|
||||
|
||||
type SelectDatabaseEvent = {|
|
||||
type: 'UpdateSelectedDatabase',
|
||||
database: number,
|
||||
|};
|
||||
|
||||
type SelectDatabaseTableEvent = {|
|
||||
type: 'UpdateSelectedDatabaseTable',
|
||||
table: string,
|
||||
|};
|
||||
|
||||
type UpdateViewModeEvent = {|
|
||||
type: 'UpdateViewMode',
|
||||
viewMode: 'data' | 'structure',
|
||||
|};
|
||||
|
||||
type UpdatePageEvent = {|
|
||||
type: 'UpdatePage',
|
||||
databaseId: number,
|
||||
table: string,
|
||||
columns: Array<string>,
|
||||
values: Array<Array<any>>,
|
||||
start: number,
|
||||
count: number,
|
||||
total: number,
|
||||
|};
|
||||
|
||||
type UpdateStructureEvent = {|
|
||||
type: 'UpdateStructure',
|
||||
databaseId: number,
|
||||
table: string,
|
||||
columns: Array<string>,
|
||||
rows: Array<Array<any>>,
|
||||
indexesColumns: Array<string>,
|
||||
indexesValues: Array<Array<any>>,
|
||||
|};
|
||||
|
||||
type NextPageEvent = {
|
||||
type: 'NextPage',
|
||||
};
|
||||
|
||||
type PreviousPageEvent = {
|
||||
type: 'PreviousPage',
|
||||
};
|
||||
|
||||
type RefreshEvent = {
|
||||
type: 'Refresh',
|
||||
};
|
||||
|
||||
type SortByChangedEvent = {
|
||||
type: 'SortByChanged',
|
||||
sortOrder: TableRowSortOrder,
|
||||
};
|
||||
|
||||
type GoToRowEvent = {
|
||||
type: 'GoToRow',
|
||||
row: number,
|
||||
};
|
||||
|
||||
function transformRow(
|
||||
columns: Array<string>,
|
||||
row: Array<Value>,
|
||||
index: number,
|
||||
): TableBodyRow {
|
||||
const transformedColumns = {};
|
||||
for (var i = 0; i < columns.length; i++) {
|
||||
transformedColumns[columns[i]] = {value: renderValue(row[i])};
|
||||
}
|
||||
return {key: String(index), columns: transformedColumns};
|
||||
}
|
||||
|
||||
function renderTable(page: ?Page, component: DatabasesPlugin) {
|
||||
if (!page) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ManagedTable
|
||||
tableKey={`databases-${page.databaseId}-${page.table}`}
|
||||
floating={false}
|
||||
columnOrder={page.columns.map(name => ({
|
||||
key: name,
|
||||
visible: true,
|
||||
}))}
|
||||
columns={page.columns.reduce((acc, val) => {
|
||||
acc[val] = {value: val, resizable: true, sortable: true};
|
||||
return acc;
|
||||
}, {})}
|
||||
zebra={true}
|
||||
rows={page.rows}
|
||||
horizontallyScrollable={true}
|
||||
onSort={(sortOrder: TableRowSortOrder) => {
|
||||
component.dispatchAction({
|
||||
type: 'SortByChanged',
|
||||
sortOrder,
|
||||
});
|
||||
}}
|
||||
initialSortOrder={component.state.currentSort}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function renderDatabaseColumns(structure: ?Structure) {
|
||||
if (!structure) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<FlexRow grow={true}>
|
||||
<ManagedTable
|
||||
floating={false}
|
||||
columnOrder={structure.columns.map(name => ({
|
||||
key: name,
|
||||
visible: true,
|
||||
}))}
|
||||
columns={structure.columns.reduce((acc, val) => {
|
||||
acc[val] = {value: val, resizable: true};
|
||||
return acc;
|
||||
}, {})}
|
||||
zebra={true}
|
||||
rows={structure.rows || []}
|
||||
horizontallyScrollable={true}
|
||||
/>
|
||||
</FlexRow>
|
||||
);
|
||||
}
|
||||
|
||||
function renderDatabaseIndexes(structure: ?Structure) {
|
||||
if (!structure) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<FlexRow grow={true}>
|
||||
<ManagedTable
|
||||
floating={false}
|
||||
columnOrder={structure.indexesColumns.map(name => ({
|
||||
key: name,
|
||||
visible: true,
|
||||
}))}
|
||||
columns={structure.indexesColumns.reduce((acc, val) => {
|
||||
acc[val] = {value: val, resizable: true};
|
||||
return acc;
|
||||
}, {})}
|
||||
zebra={true}
|
||||
rows={structure.indexesValues || []}
|
||||
horizontallyScrollable={true}
|
||||
/>
|
||||
</FlexRow>
|
||||
);
|
||||
}
|
||||
|
||||
type PageInfoProps = {
|
||||
currentRow: number,
|
||||
count: number,
|
||||
totalRows: number,
|
||||
onChange: (currentRow: number, count: number) => void,
|
||||
};
|
||||
|
||||
class PageInfo extends Component<
|
||||
PageInfoProps,
|
||||
{isOpen: boolean, inputValue: string},
|
||||
> {
|
||||
constructor(props: PageInfoProps) {
|
||||
super(props);
|
||||
this.state = {isOpen: false, inputValue: String(props.currentRow)};
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
this.setState({isOpen: true});
|
||||
}
|
||||
|
||||
onInputChanged(e) {
|
||||
this.setState({inputValue: e.target.value});
|
||||
}
|
||||
|
||||
onSubmit(e: SyntheticKeyboardEvent<>) {
|
||||
if (e.key === 'Enter') {
|
||||
const rowNumber = parseInt(this.state.inputValue);
|
||||
console.log(rowNumber);
|
||||
this.props.onChange(rowNumber - 1, this.props.count);
|
||||
this.setState({isOpen: false});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<FlexRow grow={true} alignItems={'center'}>
|
||||
<div style={{flex: 1}} />
|
||||
<Text>
|
||||
{this.props.count === this.props.totalRows
|
||||
? `${this.props.count} `
|
||||
: `${this.props.currentRow + 1}-${this.props.currentRow +
|
||||
this.props.count} `}
|
||||
of {this.props.totalRows} rows
|
||||
</Text>
|
||||
<div style={{flex: 1}} />
|
||||
{this.state.isOpen ? (
|
||||
<Input
|
||||
tabIndex={1}
|
||||
placeholder={this.props.currentRow + 1}
|
||||
onChange={this.onInputChanged.bind(this)}
|
||||
onKeyDown={this.onSubmit.bind(this)}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
style={{textAlign: 'center'}}
|
||||
onClick={this.onOpen.bind(this)}>
|
||||
Go To Row
|
||||
</Button>
|
||||
)}
|
||||
</FlexRow>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default class DatabasesPlugin extends FlipperPlugin<
|
||||
DatabasesPluginState,
|
||||
Actions,
|
||||
> {
|
||||
databaseClient: DatabaseClient;
|
||||
|
||||
state: DatabasesPluginState = {
|
||||
selectedDatabase: 0,
|
||||
selectedDatabaseTable: null,
|
||||
pageRowNumber: 0,
|
||||
databases: [],
|
||||
viewMode: 'data',
|
||||
error: null,
|
||||
currentPage: null,
|
||||
currentStructure: null,
|
||||
currentSort: null,
|
||||
};
|
||||
|
||||
reducers = [
|
||||
[
|
||||
'UpdateDatabases',
|
||||
(
|
||||
state: DatabasesPluginState,
|
||||
results: UpdateDatabasesEvent,
|
||||
): DatabasesPluginState => {
|
||||
const updates = results.databases;
|
||||
const databases = updates;
|
||||
const selectedDatabase =
|
||||
state.selectedDatabase || Object.values(databases)[0]
|
||||
? // $FlowFixMe
|
||||
Object.values(databases)[0].id
|
||||
: 0;
|
||||
const selectedTable = databases[selectedDatabase - 1].tables[0];
|
||||
const sameTableSelected =
|
||||
selectedDatabase === state.selectedDatabase &&
|
||||
selectedTable === state.selectedDatabaseTable;
|
||||
return {
|
||||
...state,
|
||||
databases,
|
||||
selectedDatabase: selectedDatabase,
|
||||
selectedDatabaseTable: selectedTable,
|
||||
pageRowNumber: 0,
|
||||
currentPage: sameTableSelected ? state.currentPage : null,
|
||||
currentStructure: null,
|
||||
currentSort: sameTableSelected ? state.currentSort : null,
|
||||
};
|
||||
},
|
||||
],
|
||||
[
|
||||
'UpdateSelectedDatabase',
|
||||
(
|
||||
state: DatabasesPluginState,
|
||||
event: SelectDatabaseEvent,
|
||||
): DatabasesPluginState => {
|
||||
return {
|
||||
...state,
|
||||
selectedDatabase: event.database,
|
||||
selectedDatabaseTable:
|
||||
state.databases[event.database - 1].tables[0] || null,
|
||||
pageRowNumber: 0,
|
||||
currentPage: null,
|
||||
currentStructure: null,
|
||||
currentSort: null,
|
||||
};
|
||||
},
|
||||
],
|
||||
[
|
||||
'UpdateSelectedDatabaseTable',
|
||||
(
|
||||
state: DatabasesPluginState,
|
||||
event: SelectDatabaseTableEvent,
|
||||
): DatabasesPluginState => {
|
||||
return {
|
||||
...state,
|
||||
selectedDatabaseTable: event.table,
|
||||
pageRowNumber: 0,
|
||||
currentPage: null,
|
||||
currentStructure: null,
|
||||
currentSort: null,
|
||||
};
|
||||
},
|
||||
],
|
||||
[
|
||||
'UpdateViewMode',
|
||||
(
|
||||
state: DatabasesPluginState,
|
||||
event: UpdateViewModeEvent,
|
||||
): DatabasesPluginState => {
|
||||
return {
|
||||
...state,
|
||||
viewMode: event.viewMode,
|
||||
};
|
||||
},
|
||||
],
|
||||
[
|
||||
'UpdatePage',
|
||||
(
|
||||
state: DatabasesPluginState,
|
||||
event: UpdatePageEvent,
|
||||
): DatabasesPluginState => {
|
||||
return {
|
||||
...state,
|
||||
currentPage: {
|
||||
rows: event.values.map((row: Array<Value>, index: number) =>
|
||||
transformRow(event.columns, row, index),
|
||||
),
|
||||
...event,
|
||||
},
|
||||
};
|
||||
},
|
||||
],
|
||||
[
|
||||
'UpdateStructure',
|
||||
(
|
||||
state: DatabasesPluginState,
|
||||
event: UpdateStructureEvent,
|
||||
): DatabasesPluginState => {
|
||||
return {
|
||||
...state,
|
||||
currentStructure: {
|
||||
databaseId: event.databaseId,
|
||||
table: event.table,
|
||||
columns: event.columns,
|
||||
rows: event.rows.map((row: Array<Value>, index: number) =>
|
||||
transformRow(event.columns, row, index),
|
||||
),
|
||||
indexesColumns: event.indexesColumns,
|
||||
indexesValues: event.indexesValues.map(
|
||||
(row: Array<Value>, index: number) =>
|
||||
transformRow(event.columns, row, index),
|
||||
),
|
||||
},
|
||||
};
|
||||
},
|
||||
],
|
||||
[
|
||||
'NextPage',
|
||||
(
|
||||
state: DatabasesPluginState,
|
||||
event: UpdatePageEvent,
|
||||
): DatabasesPluginState => {
|
||||
return {
|
||||
...state,
|
||||
pageRowNumber: state.pageRowNumber + PAGE_SIZE,
|
||||
currentPage: null,
|
||||
};
|
||||
},
|
||||
],
|
||||
[
|
||||
'PreviousPage',
|
||||
(
|
||||
state: DatabasesPluginState,
|
||||
event: UpdatePageEvent,
|
||||
): DatabasesPluginState => {
|
||||
return {
|
||||
...state,
|
||||
pageRowNumber: Math.max(state.pageRowNumber - PAGE_SIZE, 0),
|
||||
currentPage: null,
|
||||
};
|
||||
},
|
||||
],
|
||||
[
|
||||
'GoToRow',
|
||||
(
|
||||
state: DatabasesPluginState,
|
||||
event: GoToRowEvent,
|
||||
): DatabasesPluginState => {
|
||||
if (!state.currentPage) {
|
||||
return state;
|
||||
}
|
||||
const destinationRow =
|
||||
event.row < 0
|
||||
? 0
|
||||
: event.row >= state.currentPage.total - PAGE_SIZE
|
||||
? Math.max(state.currentPage.total - PAGE_SIZE, 0)
|
||||
: event.row;
|
||||
return {
|
||||
...state,
|
||||
pageRowNumber: destinationRow,
|
||||
currentPage: null,
|
||||
};
|
||||
},
|
||||
],
|
||||
[
|
||||
'Refresh',
|
||||
(
|
||||
state: DatabasesPluginState,
|
||||
event: RefreshEvent,
|
||||
): DatabasesPluginState => {
|
||||
return {
|
||||
...state,
|
||||
currentPage: null,
|
||||
};
|
||||
},
|
||||
],
|
||||
[
|
||||
'UpdateViewMode',
|
||||
(
|
||||
state: DatabasesPluginState,
|
||||
event: UpdateViewModeEvent,
|
||||
): DatabasesPluginState => {
|
||||
return {
|
||||
...state,
|
||||
viewMode: event.viewMode,
|
||||
};
|
||||
},
|
||||
],
|
||||
[
|
||||
'SortByChanged',
|
||||
(state: DatabasesPluginState, event: SortByChangedEvent) => {
|
||||
return {
|
||||
...state,
|
||||
currentSort: event.sortOrder,
|
||||
pageRowNumber: 0,
|
||||
currentPage: null,
|
||||
};
|
||||
},
|
||||
],
|
||||
].reduce((acc, val) => {
|
||||
const name = val[0];
|
||||
const f = val[1];
|
||||
|
||||
acc[name] = (previousState, event) => {
|
||||
const newState = f(previousState, event);
|
||||
this.onStateChanged(previousState, newState);
|
||||
return newState;
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
onStateChanged(
|
||||
previousState: DatabasesPluginState,
|
||||
newState: DatabasesPluginState,
|
||||
) {
|
||||
const databaseId = newState.selectedDatabase;
|
||||
const table = newState.selectedDatabaseTable;
|
||||
if (
|
||||
newState.viewMode === 'data' &&
|
||||
newState.currentPage === null &&
|
||||
databaseId &&
|
||||
table
|
||||
) {
|
||||
this.databaseClient
|
||||
.getTableData({
|
||||
count: PAGE_SIZE,
|
||||
databaseId: newState.selectedDatabase,
|
||||
order: newState.currentSort?.key,
|
||||
reverse: (newState.currentSort?.direction || 'up') === 'down',
|
||||
table: table,
|
||||
start: newState.pageRowNumber,
|
||||
})
|
||||
.then(data => {
|
||||
console.log(data);
|
||||
this.dispatchAction({
|
||||
type: 'UpdatePage',
|
||||
databaseId: databaseId,
|
||||
table: table,
|
||||
columns: data.columns,
|
||||
values: data.values,
|
||||
start: data.start,
|
||||
count: data.count,
|
||||
total: data.total,
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
this.setState({error: e});
|
||||
});
|
||||
}
|
||||
if (
|
||||
newState.viewMode === 'structure' &&
|
||||
newState.currentStructure === null &&
|
||||
databaseId &&
|
||||
table
|
||||
) {
|
||||
this.databaseClient
|
||||
.getTableStructure({
|
||||
databaseId: databaseId,
|
||||
table: table,
|
||||
})
|
||||
.then(data => {
|
||||
console.log(data);
|
||||
this.dispatchAction({
|
||||
type: 'UpdateStructure',
|
||||
databaseId: databaseId,
|
||||
table: table,
|
||||
columns: data.structureColumns,
|
||||
rows: data.structureValues,
|
||||
indexesColumns: data.indexesColumns,
|
||||
indexesValues: data.indexesValues,
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
this.setState({error: e});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
this.databaseClient = new DatabaseClient(this.client);
|
||||
this.databaseClient.getDatabases({}).then(databases => {
|
||||
console.log(databases);
|
||||
this.dispatchAction({
|
||||
type: 'UpdateDatabases',
|
||||
databases,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onDataClicked = () => {
|
||||
this.dispatchAction({type: 'UpdateViewMode', viewMode: 'data'});
|
||||
};
|
||||
|
||||
onStructureClicked = () => {
|
||||
this.dispatchAction({type: 'UpdateViewMode', viewMode: 'structure'});
|
||||
};
|
||||
|
||||
onRefreshClicked = () => {
|
||||
this.dispatchAction({type: 'Refresh'});
|
||||
};
|
||||
|
||||
onDatabaseSelected = (selected: string) => {
|
||||
const dbId = this.state.databases.find(x => x.name === selected)?.id || 0;
|
||||
this.dispatchAction({
|
||||
database: dbId,
|
||||
type: 'UpdateSelectedDatabase',
|
||||
});
|
||||
};
|
||||
|
||||
onDatabaseTableSelected = (selected: string) => {
|
||||
this.dispatchAction({
|
||||
table: selected,
|
||||
type: 'UpdateSelectedDatabaseTable',
|
||||
});
|
||||
};
|
||||
|
||||
onNextPageClicked = () => {
|
||||
this.dispatchAction({type: 'NextPage'});
|
||||
};
|
||||
|
||||
onPreviousPageClicked = () => {
|
||||
this.dispatchAction({type: 'PreviousPage'});
|
||||
};
|
||||
|
||||
onGoToRow = (row: number, count: number) => {
|
||||
this.dispatchAction({type: 'GoToRow', row: row});
|
||||
};
|
||||
|
||||
renderStructure() {
|
||||
return [
|
||||
renderDatabaseColumns(this.state.currentStructure),
|
||||
renderDatabaseIndexes(this.state.currentStructure),
|
||||
];
|
||||
}
|
||||
|
||||
render() {
|
||||
const tableOptions =
|
||||
(this.state.selectedDatabase &&
|
||||
this.state.databases[this.state.selectedDatabase - 1] &&
|
||||
this.state.databases[this.state.selectedDatabase - 1].tables.reduce(
|
||||
(options, tableName) => ({...options, [tableName]: tableName}),
|
||||
{},
|
||||
)) ||
|
||||
{};
|
||||
|
||||
return (
|
||||
<FlexColumn style={{flex: 1}}>
|
||||
<Toolbar position="top" style={{paddingLeft: 8}}>
|
||||
<BoldSpan style={{marginRight: 16}}>Database</BoldSpan>
|
||||
<Select
|
||||
options={this.state.databases
|
||||
.map(x => x.name)
|
||||
.reduce((obj, item) => {
|
||||
obj[item] = item;
|
||||
return obj;
|
||||
}, {})}
|
||||
selected={String(this.state.selectedDatabase)}
|
||||
onChange={this.onDatabaseSelected}
|
||||
/>
|
||||
<BoldSpan style={{marginLeft: 16, marginRight: 16}}>Table</BoldSpan>
|
||||
<Select
|
||||
options={tableOptions}
|
||||
selected={this.state.selectedDatabaseTable}
|
||||
onChange={this.onDatabaseTableSelected}
|
||||
/>
|
||||
<div grow={true} />
|
||||
<Button onClick={this.onRefreshClicked}>Refresh</Button>
|
||||
<Button style={{marginLeft: 'auto', display: 'none'}}>
|
||||
Execute SQL
|
||||
</Button>
|
||||
</Toolbar>
|
||||
<FlexColumn grow={true}>
|
||||
{this.state.viewMode === 'data'
|
||||
? renderTable(this.state.currentPage, this)
|
||||
: this.renderStructure()}
|
||||
</FlexColumn>
|
||||
<Toolbar position="bottom" style={{paddingLeft: 8}}>
|
||||
<FlexRow grow={true}>
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
icon={'data-table'}
|
||||
onClick={this.onDataClicked}
|
||||
selected={this.state.viewMode === 'data'}>
|
||||
Data
|
||||
</Button>
|
||||
<Button
|
||||
icon={'gears-two'}
|
||||
onClick={this.onStructureClicked}
|
||||
selected={this.state.viewMode === 'structure'}>
|
||||
Structure
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
{this.state.viewMode === 'data' && this.state.currentPage ? (
|
||||
<PageInfo
|
||||
currentRow={this.state.currentPage.start}
|
||||
count={this.state.currentPage.count}
|
||||
totalRows={this.state.currentPage.total}
|
||||
onChange={this.onGoToRow}
|
||||
/>
|
||||
) : null}
|
||||
{this.state.viewMode === 'data' && this.state.currentPage ? (
|
||||
<ButtonNavigation
|
||||
canGoBack={this.state.currentPage.start > 0}
|
||||
canGoForward={
|
||||
this.state.currentPage.start + this.state.currentPage.count <
|
||||
this.state.currentPage.total
|
||||
}
|
||||
onBack={this.onPreviousPageClicked}
|
||||
onForward={this.onNextPageClicked}
|
||||
/>
|
||||
) : null}
|
||||
</FlexRow>
|
||||
</Toolbar>
|
||||
{this.state.error && JSON.stringify(this.state.error)}
|
||||
</FlexColumn>
|
||||
);
|
||||
}
|
||||
}
|
||||
15
src/plugins/databases/package.json
Normal file
15
src/plugins/databases/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "Databases",
|
||||
"version": "1.0.0",
|
||||
"title": "Databases",
|
||||
"icon": "internet",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.11"
|
||||
},
|
||||
"bugs": {
|
||||
"email": "oncall+flipper@xmail.facebook.com",
|
||||
"url": "https://fb.workplace.com/groups/230455004101832/"
|
||||
}
|
||||
}
|
||||
8
src/plugins/databases/yarn.lock
Normal file
8
src/plugins/databases/yarn.lock
Normal file
@@ -0,0 +1,8 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
lodash@^4.17.11:
|
||||
version "4.17.11"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
|
||||
integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
|
||||
Reference in New Issue
Block a user