aboutsummaryrefslogtreecommitdiffstats
path: root/webui/src/pages/list/FilterToolbar.tsx
blob: 979bf5309ab85822d16d5be9ddbfbc7fd7ad66c4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
import { pipe } from '@arrows/composition';
import { LocationDescriptor } from 'history';
import React from 'react';

import Toolbar from '@material-ui/core/Toolbar';
import { makeStyles } from '@material-ui/core/styles';
import CheckCircleOutline from '@material-ui/icons/CheckCircleOutline';
import ErrorOutline from '@material-ui/icons/ErrorOutline';

import {
  Filter,
  FilterDropdown,
  FilterProps,
  parse,
  Query,
  stringify,
} from './Filter';
import { useBugCountQuery } from './FilterToolbar.generated';
import { useListIdentitiesQuery } from './ListIdentities.generated';
import { useListLabelsQuery } from './ListLabels.generated';

const useStyles = makeStyles((theme) => ({
  toolbar: {
    backgroundColor: theme.palette.primary.light,
    borderColor: theme.palette.divider,
    borderWidth: '1px 0',
    borderStyle: 'solid',
    margin: theme.spacing(0, -1),
  },
  spacer: {
    flex: 1,
  },
}));

// This prepends the filter text with a count
type CountingFilterProps = {
  query: string; // the query used as a source to count the number of element
  children: React.ReactNode;
} & FilterProps;

function CountingFilter({ query, children, ...props }: CountingFilterProps) {
  const { data, loading, error } = useBugCountQuery({
    variables: { query },
  });

  let prefix;
  if (loading) prefix = '...';
  else if (error || !data?.repository) prefix = '???';
  // TODO: better prefixes & error handling
  else prefix = data.repository.bugs.totalCount;

  return (
    <Filter {...props}>
      {prefix} {children}
    </Filter>
  );
}

type Props = {
  query: string;
  queryLocation: (query: string) => LocationDescriptor;
};

function FilterToolbar({ query, queryLocation }: Props) {
  const classes = useStyles();
  const params: Query = parse(query);
  const { data: identitiesData } = useListIdentitiesQuery();
  const { data: labelsData } = useListLabelsQuery();

  let identities: any = [];
  let labels: any = [];

  if (
    identitiesData?.repository &&
    identitiesData.repository.allIdentities &&
    identitiesData.repository.allIdentities.nodes
  ) {
    identities = identitiesData.repository.allIdentities.nodes.map((node) => [
      node.name,
      node.name,
    ]);
  }

  if (
    labelsData?.repository &&
    labelsData.repository.validLabels &&
    labelsData.repository.validLabels.nodes
  ) {
    labels = labelsData.repository.validLabels.nodes.map((node) => [
      node.name,
      node.name,
    ]);
  }

  const hasKey = (key: string): boolean =>
    params[key] && params[key].length > 0;
  const hasValue = (key: string, value: string): boolean =>
    hasKey(key) && params[key].includes(value);
  const containsValue = (key: string, value: string): boolean =>
    hasKey(key) && params[key].indexOf(value) !== -1;
  const loc = pipe(stringify, queryLocation);
  const replaceParam = (key: string, value: string) => (
    params: Query
  ): Query => ({
    ...params,
    [key]: [value],
  });
  const toggleParam = (key: string, value: string) => (
    params: Query
  ): Query => ({
    ...params,
    [key]: params[key] && params[key].includes(value) ? [] : [value],
  });
  const toggleOrAddParam = (key: string, value: string) => (
    params: Query
  ): Query => {
    const values = params[key];
    return {
      ...params,
      [key]:
        params[key] && params[key].includes(value)
          ? values.filter((v) => v !== value)
          : values
          ? [...values, value]
          : [value],
    };
  };
  const clearParam = (key: string) => (params: Query): Query => ({
    ...params,
    [key]: [],
  });

  // TODO: author/label filters
  return (
    <Toolbar className={classes.toolbar}>
      <CountingFilter
        active={hasValue('status', 'open')}
        query={pipe(
          replaceParam('status', 'open'),
          clearParam('sort'),
          stringify
        )(params)}
        to={pipe(toggleParam('status', 'open'), loc)(params)}
        icon={ErrorOutline}
      >
        open
      </CountingFilter>
      <CountingFilter
        active={hasValue('status', 'closed')}
        query={pipe(
          replaceParam('status', 'closed'),
          clearParam('sort'),
          stringify
        )(params)}
        to={pipe(toggleParam('status', 'closed'), loc)(params)}
        icon={CheckCircleOutline}
      >
        closed
      </CountingFilter>
      <div className={classes.spacer} />
      {/*
      <Filter active={hasKey('author')}>Author</Filter>
      <Filter active={hasKey('label')}>Label</Filter>
      */}
      <FilterDropdown
        dropdown={identities}
        itemActive={(key) => hasValue('author', key)}
        to={(key) => pipe(toggleOrAddParam('author', key), loc)(params)}
        hasFilter
      >
        Author
      </FilterDropdown>
      <FilterDropdown
        dropdown={labels}
        itemActive={(key) => containsValue('label', key)}
        to={(key) => pipe(toggleOrAddParam('label', key), loc)(params)}
        hasFilter
      >
        Labels
      </FilterDropdown>
      <FilterDropdown
        dropdown={[
          ['id', 'ID'],
          ['creation', 'Newest'],
          ['creation-asc', 'Oldest'],
          ['edit', 'Recently updated'],
          ['edit-asc', 'Least recently updated'],
        ]}
        itemActive={(key) => hasValue('sort', key)}
        to={(key) => pipe(toggleParam('sort', key), loc)(params)}
      >
        Sort
      </FilterDropdown>
    </Toolbar>
  );
}

export default FilterToolbar;