Skip to content
This repository was archived by the owner on Jan 22, 2019. It is now read-only.

Commit 288706e

Browse files
committed
feat: remove client settings
- Removes the client settings textarea from the options page - Removes the ability for extensions to persist settings changes in the browser's extension storage area
1 parent 9e65242 commit 288706e

File tree

8 files changed

+78
-228
lines changed

8 files changed

+78
-228
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@
113113
"dependencies": {
114114
"@slimsag/react-shortcuts": "^1.2.1",
115115
"@sourcegraph/codeintellify": "^3.9.0",
116-
"@sourcegraph/extensions-client-common": "^10.2.2",
116+
"@sourcegraph/extensions-client-common": "^10.3.1",
117117
"@sourcegraph/react-loading-spinner": "0.0.6",
118118
"@sqs/jsonc-parser": "^1.0.3",
119119
"@types/uglifyjs-webpack-plugin": "1.1.0",

src/browser/types.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,6 @@ export interface StorageItems {
6767
*/
6868
featureFlags: FeatureFlags
6969
clientConfiguration: ClientConfigurationDetails
70-
/**
71-
* Overrides settings from Sourcegraph.
72-
*/
73-
clientSettings: string
7470
}
7571

7672
interface ClientConfigurationDetails {
@@ -110,7 +106,6 @@ export const defaultStorageItems: StorageItems = {
110106
url: 'https://sourcegraph.com',
111107
},
112108
},
113-
clientSettings: '',
114109
}
115110

116111
export type StorageChange = { [key in keyof StorageItems]: chrome.storage.StorageChange }

src/libs/sourcegraph/inject.tsx

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
import { connectAsClient } from '@sourcegraph/extensions-client-common/lib/messaging'
2-
import storage from '../../browser/storage'
3-
import { updateExtensionSettings } from '../../shared/backend/extensions'
4-
51
export function injectSourcegraphApp(marker: HTMLElement): void {
62
if (document.getElementById(marker.id)) {
73
return
@@ -10,25 +6,6 @@ export function injectSourcegraphApp(marker: HTMLElement): void {
106
// Generate and insert DOM element, in case this code executes first.
117
document.body.appendChild(marker)
128

13-
connectAsClient()
14-
.then(connection => {
15-
storage.observeSync('clientSettings').subscribe(settings => {
16-
connection.sendSettings(settings)
17-
})
18-
19-
connection.onEditSetting(edit => updateExtensionSettings('Client', edit).toPromise())
20-
21-
connection.onGetSettings(
22-
() =>
23-
new Promise<string>(resolve => {
24-
storage.getSync(storageItems => {
25-
resolve(storageItems.clientSettings)
26-
})
27-
})
28-
)
29-
})
30-
.catch(error => console.error(error))
31-
329
window.addEventListener('load', () => {
3310
dispatchSourcegraphEvents()
3411
})

src/shared/backend/extensions.ts

Lines changed: 72 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,28 @@ import { UpdateExtensionSettingsArgs } from '@sourcegraph/extensions-client-comm
22
import { Controller as ExtensionsContextController } from '@sourcegraph/extensions-client-common/lib/controller'
33
import { ConfiguredExtension } from '@sourcegraph/extensions-client-common/lib/extensions/extension'
44
import { gql, graphQLContent } from '@sourcegraph/extensions-client-common/lib/graphql'
5-
import {
6-
ConfigurationCascade,
7-
ConfigurationCascadeOrError,
8-
ConfigurationSubject,
9-
gqlToCascade,
10-
mergeSettings,
11-
Settings,
12-
} from '@sourcegraph/extensions-client-common/lib/settings'
5+
import { ConfigurationSubject, gqlToCascade, Settings } from '@sourcegraph/extensions-client-common/lib/settings'
136
import { LoadingSpinner } from '@sourcegraph/react-loading-spinner'
14-
import { applyEdits } from '@sqs/jsonc-parser'
15-
import * as JSONC from '@sqs/jsonc-parser'
16-
import { removeProperty, setProperty } from '@sqs/jsonc-parser/lib/edit'
177
import { isEqual } from 'lodash'
188
import AddIcon from 'mdi-react/AddIcon'
199
import Alert from 'mdi-react/AlertIcon'
2010
import InfoIcon from 'mdi-react/InformationIcon'
2111
import MenuDown from 'mdi-react/MenuDownIcon'
2212
import Menu from 'mdi-react/MenuIcon'
2313
import SettingsIcon from 'mdi-react/SettingsIcon'
24-
import { combineLatest, from, Observable, throwError } from 'rxjs'
25-
import { distinctUntilChanged, map, mergeMap, switchMap, take } from 'rxjs/operators'
14+
import { combineLatest, Observable, Subject, throwError } from 'rxjs'
15+
import { distinctUntilChanged, map, mapTo, mergeMap, startWith, switchMap, take, tap } from 'rxjs/operators'
2616
import { MessageTransports } from 'sourcegraph/module/protocol/jsonrpc2/connection'
2717
import { TextDocumentDecoration } from 'sourcegraph/module/protocol/plainTypes'
2818
import uuid from 'uuid'
2919
import { Disposable } from 'vscode-languageserver'
30-
import storage, { StorageItems } from '../../browser/storage'
20+
import storage from '../../browser/storage'
21+
import { GQL } from '../../types/gqlschema'
3122
import { ExtensionConnectionInfo, onFirstMessage } from '../messaging'
3223
import { canFetchForURL } from '../util/context'
3324
import { getContext } from './context'
3425
import { createAggregateError, isErrorLike } from './errors'
35-
import { queryGraphQL } from './graphql'
26+
import { mutateGraphQL, queryGraphQL } from './graphql'
3627
import { sendLSPHTTPRequests } from './lsp'
3728
import { createPortMessageTransports } from './PortMessageTransports'
3829

@@ -137,45 +128,6 @@ export const applyDecoration = ({
137128
return mergeDisposables(...disposables)
138129
}
139130

140-
const storageConfigurationCascade: Observable<
141-
ConfigurationCascade<ConfigurationSubject, Settings>
142-
> = storage.observeSync('clientSettings').pipe(
143-
map(clientSettingsString => JSONC.parse(clientSettingsString || '')),
144-
map(clientSettings => ({
145-
subjects: [
146-
{
147-
subject: {
148-
id: 'Client',
149-
settingsURL: 'N/A',
150-
viewerCanAdminister: true,
151-
__typename: 'Client',
152-
displayName: 'Client',
153-
} as ConfigurationSubject,
154-
settings: clientSettings,
155-
},
156-
],
157-
merged: clientSettings || {},
158-
}))
159-
)
160-
161-
const mergeCascades = (
162-
cascadeOrError: ConfigurationCascadeOrError<ConfigurationSubject, Settings>,
163-
cascade: ConfigurationCascade<ConfigurationSubject, Settings>
164-
): ConfigurationCascadeOrError<ConfigurationSubject, Settings> => ({
165-
subjects:
166-
cascadeOrError.subjects === null
167-
? cascade.subjects
168-
: isErrorLike(cascadeOrError.subjects)
169-
? cascadeOrError.subjects
170-
: [...cascadeOrError.subjects, ...cascade.subjects],
171-
merged:
172-
cascadeOrError.merged === null
173-
? cascade.merged
174-
: isErrorLike(cascadeOrError.merged)
175-
? cascadeOrError.merged
176-
: mergeSettings([cascadeOrError.merged, cascade.merged]),
177-
})
178-
179131
const configurationCascadeFragment = gql`
180132
fragment ConfigurationCascadeFields on ConfigurationCascade {
181133
subjects {
@@ -210,12 +162,21 @@ const configurationCascadeFragment = gql`
210162
}
211163
`
212164

165+
/** A subject that emits whenever the configuration cascade must be refreshed from the Sourcegraph instance. */
166+
const configurationCascadeRefreshes = new Subject<void>()
167+
213168
/**
214169
* Always represents the entire configuration cascade; i.e., it contains the
215170
* individual configs from the various config subjects (orgs, user, etc.).
216171
*/
217-
export const gqlConfigurationCascade = storage.observeSync('sourcegraphURL').pipe(
218-
switchMap(url =>
172+
export const configurationCascade = combineLatest(
173+
storage.observeSync('sourcegraphURL'),
174+
configurationCascadeRefreshes.pipe(
175+
mapTo(null),
176+
startWith(null)
177+
)
178+
).pipe(
179+
switchMap(([url]) =>
219180
queryGraphQL({
220181
ctx: getContext({ repoKey: '', isRepoSpecific: false }),
221182
request: gql`
@@ -233,13 +194,6 @@ export const gqlConfigurationCascade = storage.observeSync('sourcegraphURL').pip
233194
if (!data || !data.viewerConfiguration) {
234195
throw createAggregateError(errors)
235196
}
236-
237-
for (const subject of data.viewerConfiguration.subjects) {
238-
// User/org/global settings cannot be edited from the
239-
// browser extension (only client settings can).
240-
subject.viewerCanAdminister = false
241-
}
242-
243197
return data.viewerConfiguration
244198
})
245199
)
@@ -253,8 +207,8 @@ export function createExtensionsContextController(
253207
sourcegraphLanguageServerURL.pathname = '.api/xlang'
254208

255209
return new ExtensionsContextController<ConfigurationSubject, Settings>({
256-
configurationCascade: combineLatest(gqlConfigurationCascade, storageConfigurationCascade).pipe(
257-
map(([gqlCascade, storageCascade]) => mergeCascades(gqlToCascade(gqlCascade), storageCascade)),
210+
configurationCascade: configurationCascade.pipe(
211+
map(gqlCascade => gqlToCascade(gqlCascade)),
258212
distinctUntilChanged((a, b) => isEqual(a, b))
259213
),
260214
updateExtensionSettings,
@@ -295,40 +249,62 @@ export function createExtensionsContextController(
295249
})
296250
}
297251

298-
export const updateExtensionSettings = (subjectID, args: UpdateExtensionSettingsArgs): Observable<undefined> => {
299-
if (subjectID !== 'Client') {
300-
return throwError('Cannot update settings for ' + subjectID + '.')
301-
}
302-
return from(
303-
new Promise<StorageItems>(resolve => storage.getSync(storageItems => resolve(storageItems))).then(
304-
storageItems => {
305-
let clientSettings = storageItems.clientSettings
252+
// TODO(sqs): copied from sourcegraph/sourcegraph temporarily
253+
function updateExtensionSettings(subject: string, args: UpdateExtensionSettingsArgs): Observable<void> {
254+
return configurationCascade.pipe(
255+
take(1),
256+
switchMap(configurationCascade => {
257+
const subjectConfig = configurationCascade.subjects.find(s => s.id === subject)
258+
if (!subjectConfig) {
259+
throw new Error(`no configuration subject: ${subject}`)
260+
}
261+
const lastID = subjectConfig.latestSettings ? subjectConfig.latestSettings.id : null
262+
263+
let edit: GQL.IConfigurationEdit
264+
if ('edit' in args && args.edit) {
265+
edit = { keyPath: toGQLKeyPath(args.edit.path), value: args.edit.value }
266+
} else if ('extensionID' in args) {
267+
edit = {
268+
keyPath: toGQLKeyPath(['extensions', args.extensionID]),
269+
value: typeof args.enabled === 'boolean' ? args.enabled : null,
270+
}
271+
} else {
272+
throw new Error('no edit')
273+
}
306274

307-
const format = { tabSize: 2, insertSpaces: true, eol: '\n' }
275+
return editConfiguration(subject, lastID, edit)
276+
})
277+
)
278+
}
308279

309-
if ('edit' in args && args.edit) {
310-
clientSettings = applyEdits(
311-
clientSettings,
312-
// TODO(chris): remove `.slice()` (which guards against
313-
// mutation) once
314-
// https://github.com/Microsoft/node-jsonc-parser/pull/12
315-
// is merged in.
316-
setProperty(clientSettings, args.edit.path.slice(), args.edit.value, format)
317-
)
318-
} else if ('extensionID' in args) {
319-
clientSettings = applyEdits(
320-
clientSettings,
321-
typeof args.enabled === 'boolean'
322-
? setProperty(clientSettings, ['extensions', args.extensionID], args.enabled, format)
323-
: removeProperty(clientSettings, ['extensions', args.extensionID], format)
324-
)
280+
// TODO(sqs): copied from sourcegraph/sourcegraph temporarily
281+
function editConfiguration(subject: GQL.ID, lastID: number | null, edit: GQL.IConfigurationEdit): Observable<void> {
282+
return mutateGraphQL({
283+
ctx: getContext({ repoKey: '', isRepoSpecific: false }),
284+
request: `
285+
mutation EditSettings($subject: ID!, $lastID: Int, $edit: ConfigurationEdit!) {
286+
configurationMutation(input: { subject: $subject, lastID: $lastID }) {
287+
editConfiguration(edit: $edit) {
288+
empty {
289+
alwaysNil
290+
}
291+
}
325292
}
326-
return new Promise<undefined>(resolve =>
327-
storage.setSync({ clientSettings }, () => {
328-
resolve(undefined)
329-
})
330-
)
331293
}
332-
)
294+
`,
295+
variables: { subject, lastID, edit },
296+
}).pipe(
297+
map(({ errors }) => {
298+
if (errors && errors.length > 0) {
299+
throw createAggregateError(errors)
300+
}
301+
}),
302+
map(() => undefined),
303+
tap(() => configurationCascadeRefreshes.next())
333304
)
334305
}
306+
307+
// TODO(sqs): copied from sourcegraph/sourcegraph temporarily
308+
function toGQLKeyPath(keyPath: (string | number)[]): GQL.IKeyPathSegment[] {
309+
return keyPath.map(v => (typeof v === 'string' ? { property: v } : { index: v }))
310+
}

src/shared/components/options/BrowserSettingsEditor.tsx

Lines changed: 0 additions & 69 deletions
This file was deleted.

src/shared/components/options/ClientSettingsCard.tsx

Lines changed: 0 additions & 20 deletions
This file was deleted.

0 commit comments

Comments
 (0)