search.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import React from 'react';
  2. import { connect } from 'react-redux';
  3. import { isEmpty } from 'lodash';
  4. import { Search } from 'weaveworks-ui-components';
  5. import styled from 'styled-components';
  6. import {
  7. blurSearch, updateSearch, toggleHelp
  8. } from '../actions/app-actions';
  9. import {
  10. focusSearch
  11. } from '../actions/request-actions';
  12. import { searchMatchCountByTopologySelector } from '../selectors/search';
  13. import { isResourceViewModeSelector } from '../selectors/topology';
  14. import { slugify } from '../utils/string-utils';
  15. import { isTopologyNodeCountZero } from '../utils/topology-utils';
  16. import { trackAnalyticsEvent } from '../utils/tracking-utils';
  17. const SearchWrapper = styled.div`
  18. margin: 0 8px;
  19. min-width: 160px;
  20. text-align: right;
  21. `;
  22. const SearchContainer = styled.div`
  23. display: inline-block;
  24. position: relative;
  25. pointer-events: all;
  26. line-height: 100%;
  27. max-width: 400px;
  28. width: 100%;
  29. `;
  30. const SearchHint = styled.div`
  31. font-size: ${props => props.theme.fontSizes.tiny};
  32. color: ${props => props.theme.colors.purple400};
  33. transition: transform 0.3s 0s ease-in-out, opacity 0.3s 0s ease-in-out;
  34. text-align: left;
  35. margin-top: 3px;
  36. padding: 0 1em;
  37. opacity: 0;
  38. ${props => props.active && `
  39. opacity: 1;
  40. `};
  41. `;
  42. const SearchHintIcon = styled.span`
  43. font-size: ${props => props.theme.fontSizes.normal};
  44. cursor: pointer;
  45. &:hover {
  46. color: ${props => props.theme.colors.purple600};
  47. }
  48. `;
  49. function shortenHintLabel(text) {
  50. return text
  51. .split(' ')[0]
  52. .toLowerCase()
  53. .substr(0, 12);
  54. }
  55. // dynamic hint based on node names
  56. function getHint(nodes) {
  57. // let label = 'mycontainer';
  58. let label = 'myservice';
  59. let metadataLabel = 'ip';
  60. let metadataValue = '10.1.0.1';
  61. const node = nodes.filter(n => !n.get('pseudo') && n.has('metadata')).last();
  62. if (node) {
  63. [label] = shortenHintLabel(node.get('label')).split('.');
  64. if (node.get('metadata')) {
  65. const metadataField = node.get('metadata').first();
  66. metadataLabel = shortenHintLabel(slugify(metadataField.get('label')))
  67. .split('.').pop();
  68. metadataValue = shortenHintLabel(metadataField.get('value'));
  69. }
  70. }
  71. // return `Try "${label}", "${metadataLabel}:${metadataValue}", or "cpu > 2%".`;
  72. return `Try "${label}", "error>20%".`;
  73. // return 'Try "myservice","error>20%".'
  74. }
  75. class SearchComponent extends React.Component {
  76. handleChange = (searchQuery, pinnedSearches) => {
  77. trackAnalyticsEvent('scope.search.query.change', {
  78. layout: this.props.topologyViewMode,
  79. parentTopologyId: this.props.currentTopology.get('parentId'),
  80. topologyId: this.props.currentTopology.get('id'),
  81. });
  82. this.props.updateSearch(searchQuery, pinnedSearches);
  83. }
  84. render() {
  85. const {
  86. searchHint, searchMatchesCount, searchQuery, pinnedSearches, topologiesLoaded,
  87. isResourceViewMode, isTopologyEmpty,
  88. } = this.props;
  89. return (
  90. <SearchWrapper>
  91. <SearchContainer title={searchMatchesCount ? `${searchMatchesCount} matches` : undefined}>
  92. <Search
  93. placeholder="search"
  94. query={searchQuery}
  95. pinnedTerms={pinnedSearches}
  96. disabled={topologiesLoaded && !isResourceViewMode && isTopologyEmpty}
  97. onChange={this.handleChange}
  98. onFocus={this.props.focusSearch}
  99. onBlur={this.props.blurSearch}
  100. />
  101. <SearchHint active={this.props.searchFocused && isEmpty(pinnedSearches)}>
  102. {searchHint}
  103. {' '}
  104. {/* <SearchHintIcon
  105. className="fa fa-question-circle"
  106. onMouseDown={this.props.toggleHelp}
  107. /> */}
  108. </SearchHint>
  109. </SearchContainer>
  110. </SearchWrapper>
  111. );
  112. }
  113. }
  114. export default connect(
  115. state => ({
  116. currentTopology: state.get('currentTopology'),
  117. isResourceViewMode: isResourceViewModeSelector(state),
  118. isTopologyEmpty: isTopologyNodeCountZero(state),
  119. pinnedSearches: state.get('pinnedSearches').toJS(),
  120. searchFocused: state.get('searchFocused'),
  121. searchHint: getHint(state.get('nodes')),
  122. searchMatchesCount: searchMatchCountByTopologySelector(state)
  123. .reduce((count, topologyMatchCount) => count + topologyMatchCount, 0),
  124. searchQuery: state.get('searchQuery'),
  125. topologiesLoaded: state.get('topologiesLoaded'),
  126. topologyViewMode: state.get('topologyViewMode'),
  127. }),
  128. {
  129. blurSearch, focusSearch, toggleHelp, updateSearch
  130. }
  131. )(SearchComponent);