|
@@ -1,7 +1,6 @@
|
|
import PropTypes from 'prop-types';
|
|
import PropTypes from 'prop-types';
|
|
import React from 'react';
|
|
import React from 'react';
|
|
import {browserHistory} from 'react-router';
|
|
import {browserHistory} from 'react-router';
|
|
-import {isEqual} from 'lodash';
|
|
|
|
|
|
|
|
import {
|
|
import {
|
|
addErrorMessage,
|
|
addErrorMessage,
|
|
@@ -18,8 +17,9 @@ import Result from './result';
|
|
import Intro from './intro';
|
|
import Intro from './intro';
|
|
import EarlyAdopterMessage from './earlyAdopterMessage';
|
|
import EarlyAdopterMessage from './earlyAdopterMessage';
|
|
import NewQuery from './sidebar/newQuery';
|
|
import NewQuery from './sidebar/newQuery';
|
|
-import QueryRead from './sidebar/queryRead';
|
|
|
|
|
|
+import EditSavedQuery from './sidebar/editSavedQuery';
|
|
import SavedQueryList from './sidebar/savedQueryList';
|
|
import SavedQueryList from './sidebar/savedQueryList';
|
|
|
|
+import QueryPanel from './sidebar/queryPanel';
|
|
|
|
|
|
import createResultManager from './resultManager';
|
|
import createResultManager from './resultManager';
|
|
import {
|
|
import {
|
|
@@ -27,7 +27,6 @@ import {
|
|
getQueryFromQueryString,
|
|
getQueryFromQueryString,
|
|
deleteSavedQuery,
|
|
deleteSavedQuery,
|
|
updateSavedQuery,
|
|
updateSavedQuery,
|
|
- parseSavedQuery,
|
|
|
|
} from './utils';
|
|
} from './utils';
|
|
import {isValidCondition} from './conditions/utils';
|
|
import {isValidCondition} from './conditions/utils';
|
|
import {isValidAggregation} from './aggregations/utils';
|
|
import {isValidAggregation} from './aggregations/utils';
|
|
@@ -38,11 +37,8 @@ import {
|
|
TopBar,
|
|
TopBar,
|
|
Sidebar,
|
|
Sidebar,
|
|
SidebarTabs,
|
|
SidebarTabs,
|
|
- SavedQueryTitle,
|
|
|
|
- SavedQueryAction,
|
|
|
|
PageTitle,
|
|
PageTitle,
|
|
- EditableName,
|
|
|
|
- BackToQueryList,
|
|
|
|
|
|
+ SavedQueryWrapper,
|
|
} from './styles';
|
|
} from './styles';
|
|
|
|
|
|
import {trackQuery} from './analytics';
|
|
import {trackQuery} from './analytics';
|
|
@@ -51,9 +47,12 @@ export default class OrganizationDiscover extends React.Component {
|
|
static propTypes = {
|
|
static propTypes = {
|
|
organization: SentryTypes.Organization.isRequired,
|
|
organization: SentryTypes.Organization.isRequired,
|
|
queryBuilder: PropTypes.object.isRequired,
|
|
queryBuilder: PropTypes.object.isRequired,
|
|
- savedQuery: SentryTypes.DiscoverSavedQuery, // Provided if it's a saved search
|
|
|
|
|
|
+ // savedQuery and isEditingSavedQuery are provided if it's a saved query
|
|
|
|
+ savedQuery: SentryTypes.DiscoverSavedQuery,
|
|
|
|
+ isEditingSavedQuery: PropTypes.bool,
|
|
updateSavedQueryData: PropTypes.func.isRequired,
|
|
updateSavedQueryData: PropTypes.func.isRequired,
|
|
view: PropTypes.oneOf(['query', 'saved']),
|
|
view: PropTypes.oneOf(['query', 'saved']),
|
|
|
|
+ toggleEditMode: PropTypes.func.isRequired,
|
|
};
|
|
};
|
|
|
|
|
|
constructor(props) {
|
|
constructor(props) {
|
|
@@ -63,7 +62,7 @@ export default class OrganizationDiscover extends React.Component {
|
|
resultManager,
|
|
resultManager,
|
|
data: resultManager.getAll(),
|
|
data: resultManager.getAll(),
|
|
isFetchingQuery: false,
|
|
isFetchingQuery: false,
|
|
- isEditingSavedQuery: false,
|
|
|
|
|
|
+ isEditingSavedQuery: props.isEditingSavedQuery,
|
|
savedQueryName: null,
|
|
savedQueryName: null,
|
|
view: props.view || 'query',
|
|
view: props.view || 'query',
|
|
};
|
|
};
|
|
@@ -76,7 +75,7 @@ export default class OrganizationDiscover extends React.Component {
|
|
}
|
|
}
|
|
|
|
|
|
componentWillReceiveProps(nextProps) {
|
|
componentWillReceiveProps(nextProps) {
|
|
- const {queryBuilder, location: {search}, savedQuery} = nextProps;
|
|
|
|
|
|
+ const {queryBuilder, location: {search}, savedQuery, isEditingSavedQuery} = nextProps;
|
|
const currentSearch = this.props.location.search;
|
|
const currentSearch = this.props.location.search;
|
|
const {resultManager} = this.state;
|
|
const {resultManager} = this.state;
|
|
|
|
|
|
@@ -85,6 +84,11 @@ export default class OrganizationDiscover extends React.Component {
|
|
this.runQuery();
|
|
this.runQuery();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ if (isEditingSavedQuery !== this.props.isEditingSavedQuery) {
|
|
|
|
+ this.setState({isEditingSavedQuery});
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
if (currentSearch === search) {
|
|
if (currentSearch === search) {
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
@@ -169,7 +173,7 @@ export default class OrganizationDiscover extends React.Component {
|
|
});
|
|
});
|
|
};
|
|
};
|
|
|
|
|
|
- onFetchPage(nextOrPrev) {
|
|
|
|
|
|
+ onFetchPage = nextOrPrev => {
|
|
this.setState({isFetchingQuery: true});
|
|
this.setState({isFetchingQuery: true});
|
|
return this.state.resultManager
|
|
return this.state.resultManager
|
|
.fetchPage(nextOrPrev)
|
|
.fetchPage(nextOrPrev)
|
|
@@ -181,7 +185,7 @@ export default class OrganizationDiscover extends React.Component {
|
|
addErrorMessage(message);
|
|
addErrorMessage(message);
|
|
this.setState({isFetchingQuery: false});
|
|
this.setState({isFetchingQuery: false});
|
|
});
|
|
});
|
|
- }
|
|
|
|
|
|
+ };
|
|
|
|
|
|
toggleSidebar = view => {
|
|
toggleSidebar = view => {
|
|
if (view !== this.state.view) {
|
|
if (view !== this.state.view) {
|
|
@@ -193,16 +197,6 @@ export default class OrganizationDiscover extends React.Component {
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
- toggleEditMode = () => {
|
|
|
|
- this.setState(state => {
|
|
|
|
- const isEditMode = !state.isEditingSavedQuery;
|
|
|
|
- return {
|
|
|
|
- isEditingSavedQuery: isEditMode,
|
|
|
|
- savedQueryName: isEditMode ? this.props.savedQuery.name : null,
|
|
|
|
- };
|
|
|
|
- });
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
loadSavedQueries = () => {
|
|
loadSavedQueries = () => {
|
|
browserHistory.push({
|
|
browserHistory.push({
|
|
pathname: `/organizations/${this.props.organization.slug}/discover/`,
|
|
pathname: `/organizations/${this.props.organization.slug}/discover/`,
|
|
@@ -226,6 +220,8 @@ export default class OrganizationDiscover extends React.Component {
|
|
|
|
|
|
deleteSavedQuery = () => {
|
|
deleteSavedQuery = () => {
|
|
const {organization, savedQuery} = this.props;
|
|
const {organization, savedQuery} = this.props;
|
|
|
|
+ const {resultManager} = this.state;
|
|
|
|
+
|
|
deleteSavedQuery(organization, savedQuery.id)
|
|
deleteSavedQuery(organization, savedQuery.id)
|
|
.then(() => {
|
|
.then(() => {
|
|
addSuccessMessage(
|
|
addSuccessMessage(
|
|
@@ -233,6 +229,7 @@ export default class OrganizationDiscover extends React.Component {
|
|
name: savedQuery.name,
|
|
name: savedQuery.name,
|
|
})
|
|
})
|
|
);
|
|
);
|
|
|
|
+ resultManager.reset();
|
|
this.loadSavedQueries();
|
|
this.loadSavedQueries();
|
|
})
|
|
})
|
|
.catch(() => {
|
|
.catch(() => {
|
|
@@ -241,38 +238,25 @@ export default class OrganizationDiscover extends React.Component {
|
|
});
|
|
});
|
|
};
|
|
};
|
|
|
|
|
|
- updateSavedQueryName = savedQueryName => {
|
|
|
|
- this.setState({savedQueryName});
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- updateSavedQuery = () => {
|
|
|
|
- const {queryBuilder, savedQuery, organization} = this.props;
|
|
|
|
|
|
+ updateSavedQuery = name => {
|
|
|
|
+ const {queryBuilder, savedQuery, organization, toggleEditMode} = this.props;
|
|
const query = queryBuilder.getInternal();
|
|
const query = queryBuilder.getInternal();
|
|
- const hasChanged =
|
|
|
|
- !isEqual(query, parseSavedQuery(savedQuery)) ||
|
|
|
|
- savedQuery.name !== this.state.savedQueryName;
|
|
|
|
-
|
|
|
|
- const data = {...query, name: this.state.savedQueryName};
|
|
|
|
-
|
|
|
|
- if (hasChanged) {
|
|
|
|
- updateSavedQuery(organization, savedQuery.id, data)
|
|
|
|
- .then(resp => {
|
|
|
|
- addSuccessMessage(t('Updated query'));
|
|
|
|
- this.toggleEditMode(); // Return to read-only mode
|
|
|
|
- this.props.updateSavedQueryData(resp);
|
|
|
|
- this.runQuery();
|
|
|
|
- })
|
|
|
|
- .catch(() => {
|
|
|
|
- addErrorMessage(t('Could not update query'));
|
|
|
|
- });
|
|
|
|
- } else {
|
|
|
|
- this.toggleEditMode(); // Return to read-only mode
|
|
|
|
- }
|
|
|
|
|
|
+
|
|
|
|
+ const data = {...query, name};
|
|
|
|
+
|
|
|
|
+ updateSavedQuery(organization, savedQuery.id, data)
|
|
|
|
+ .then(resp => {
|
|
|
|
+ addSuccessMessage(t('Updated query'));
|
|
|
|
+ toggleEditMode(); // Return to read-only mode
|
|
|
|
+ this.props.updateSavedQueryData(resp);
|
|
|
|
+ })
|
|
|
|
+ .catch(() => {
|
|
|
|
+ addErrorMessage(t('Could not update query'));
|
|
|
|
+ });
|
|
};
|
|
};
|
|
|
|
|
|
renderSidebarNav() {
|
|
renderSidebarNav() {
|
|
- const {view, isEditingSavedQuery} = this.state;
|
|
|
|
- const {savedQuery} = this.props;
|
|
|
|
|
|
+ const {view} = this.state;
|
|
const views = [
|
|
const views = [
|
|
{id: 'query', title: t('Query')},
|
|
{id: 'query', title: t('Query')},
|
|
{id: 'saved', title: t('Saved queries')},
|
|
{id: 'saved', title: t('Saved queries')},
|
|
@@ -287,55 +271,18 @@ export default class OrganizationDiscover extends React.Component {
|
|
</li>
|
|
</li>
|
|
))}
|
|
))}
|
|
</SidebarTabs>
|
|
</SidebarTabs>
|
|
- {savedQuery && (
|
|
|
|
- <React.Fragment>
|
|
|
|
- <BackToQueryList>
|
|
|
|
- <a onClick={this.loadSavedQueries}>
|
|
|
|
- {tct('[arr] Back to saved query list', {arr: '←'})}
|
|
|
|
- </a>
|
|
|
|
- </BackToQueryList>
|
|
|
|
- <SavedQueryTitle>
|
|
|
|
- {!isEditingSavedQuery && (
|
|
|
|
- <React.Fragment>
|
|
|
|
- {savedQuery.name}
|
|
|
|
- <SavedQueryAction onClick={this.toggleEditMode}>
|
|
|
|
- {t('Edit query')}
|
|
|
|
- </SavedQueryAction>
|
|
|
|
- </React.Fragment>
|
|
|
|
- )}
|
|
|
|
- {isEditingSavedQuery && (
|
|
|
|
- <React.Fragment>
|
|
|
|
- <EditableName
|
|
|
|
- value={savedQuery.name}
|
|
|
|
- onChange={this.updateSavedQueryName}
|
|
|
|
- />
|
|
|
|
- <SavedQueryAction onClick={this.updateSavedQuery}>
|
|
|
|
- {t('Save changes')}
|
|
|
|
- </SavedQueryAction>
|
|
|
|
- <SavedQueryAction onClick={this.deleteSavedQuery}>
|
|
|
|
- {t('Delete')}
|
|
|
|
- </SavedQueryAction>
|
|
|
|
- </React.Fragment>
|
|
|
|
- )}
|
|
|
|
- </SavedQueryTitle>
|
|
|
|
- </React.Fragment>
|
|
|
|
- )}
|
|
|
|
</React.Fragment>
|
|
</React.Fragment>
|
|
);
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
render() {
|
|
render() {
|
|
const {data, isFetchingQuery, view, resultManager, isEditingSavedQuery} = this.state;
|
|
const {data, isFetchingQuery, view, resultManager, isEditingSavedQuery} = this.state;
|
|
- const {queryBuilder, organization, savedQuery} = this.props;
|
|
|
|
|
|
+
|
|
|
|
+ const {queryBuilder, organization, savedQuery, toggleEditMode} = this.props;
|
|
|
|
|
|
const currentQuery = queryBuilder.getInternal();
|
|
const currentQuery = queryBuilder.getInternal();
|
|
|
|
|
|
const shouldDisplayResult = resultManager.shouldDisplayResult();
|
|
const shouldDisplayResult = resultManager.shouldDisplayResult();
|
|
- const shouldRenderSavedList = view === 'saved' && !savedQuery;
|
|
|
|
- const shouldRenderReadMode = view === 'saved' && savedQuery && !isEditingSavedQuery;
|
|
|
|
- const shouldRenderEditMode =
|
|
|
|
- (view === 'saved' && savedQuery && isEditingSavedQuery) ||
|
|
|
|
- (view === 'query' && !savedQuery);
|
|
|
|
|
|
|
|
const projects = organization.projects.filter(project => project.isMember);
|
|
const projects = organization.projects.filter(project => project.isMember);
|
|
|
|
|
|
@@ -344,15 +291,14 @@ export default class OrganizationDiscover extends React.Component {
|
|
<Sidebar>
|
|
<Sidebar>
|
|
<PageTitle>{t('Discover')}</PageTitle>
|
|
<PageTitle>{t('Discover')}</PageTitle>
|
|
{this.renderSidebarNav()}
|
|
{this.renderSidebarNav()}
|
|
- {shouldRenderReadMode && (
|
|
|
|
- <QueryRead
|
|
|
|
- queryBuilder={queryBuilder}
|
|
|
|
- isFetchingQuery={isFetchingQuery}
|
|
|
|
- onRunQuery={this.runQuery}
|
|
|
|
- />
|
|
|
|
|
|
+ {view === 'saved' && (
|
|
|
|
+ <SavedQueryWrapper isEditing={isEditingSavedQuery}>
|
|
|
|
+ <SavedQueryList organization={organization} savedQuery={savedQuery} />
|
|
|
|
+ </SavedQueryWrapper>
|
|
)}
|
|
)}
|
|
- {shouldRenderEditMode && (
|
|
|
|
|
|
+ {view === 'query' && (
|
|
<NewQuery
|
|
<NewQuery
|
|
|
|
+ organization={organization}
|
|
queryBuilder={queryBuilder}
|
|
queryBuilder={queryBuilder}
|
|
isFetchingQuery={isFetchingQuery}
|
|
isFetchingQuery={isFetchingQuery}
|
|
onUpdateField={this.updateField}
|
|
onUpdateField={this.updateField}
|
|
@@ -360,7 +306,21 @@ export default class OrganizationDiscover extends React.Component {
|
|
onReset={this.reset}
|
|
onReset={this.reset}
|
|
/>
|
|
/>
|
|
)}
|
|
)}
|
|
- {shouldRenderSavedList && <SavedQueryList organization={organization} />}
|
|
|
|
|
|
+ {isEditingSavedQuery &&
|
|
|
|
+ savedQuery && (
|
|
|
|
+ <QueryPanel title={t('Edit Query')} onClose={toggleEditMode}>
|
|
|
|
+ <EditSavedQuery
|
|
|
|
+ savedQuery={savedQuery}
|
|
|
|
+ queryBuilder={queryBuilder}
|
|
|
|
+ isFetchingQuery={isFetchingQuery}
|
|
|
|
+ onUpdateField={this.updateField}
|
|
|
|
+ onRunQuery={this.runQuery}
|
|
|
|
+ onReset={this.reset}
|
|
|
|
+ onDeleteQuery={this.deleteSavedQuery}
|
|
|
|
+ onSaveQuery={this.updateSavedQuery}
|
|
|
|
+ />
|
|
|
|
+ </QueryPanel>
|
|
|
|
+ )}
|
|
</Sidebar>
|
|
</Sidebar>
|
|
<Body>
|
|
<Body>
|
|
<TopBar>
|
|
<TopBar>
|
|
@@ -385,10 +345,9 @@ export default class OrganizationDiscover extends React.Component {
|
|
{shouldDisplayResult && (
|
|
{shouldDisplayResult && (
|
|
<Result
|
|
<Result
|
|
data={data}
|
|
data={data}
|
|
- organization={organization}
|
|
|
|
savedQuery={savedQuery}
|
|
savedQuery={savedQuery}
|
|
- queryBuilder={queryBuilder}
|
|
|
|
- onFetchPage={this.onFetchPage.bind(this)}
|
|
|
|
|
|
+ onToggleEdit={toggleEditMode}
|
|
|
|
+ onFetchPage={this.onFetchPage}
|
|
/>
|
|
/>
|
|
)}
|
|
)}
|
|
{!shouldDisplayResult && <Intro updateQuery={this.updateFields} />}
|
|
{!shouldDisplayResult && <Intro updateQuery={this.updateFields} />}
|