@@ -2,37 +2,28 @@ import { UpdateExtensionSettingsArgs } from '@sourcegraph/extensions-client-comm
22import { Controller as ExtensionsContextController } from '@sourcegraph/extensions-client-common/lib/controller'
33import { ConfiguredExtension } from '@sourcegraph/extensions-client-common/lib/extensions/extension'
44import { 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'
136import { 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'
177import { isEqual } from 'lodash'
188import AddIcon from 'mdi-react/AddIcon'
199import Alert from 'mdi-react/AlertIcon'
2010import InfoIcon from 'mdi-react/InformationIcon'
2111import MenuDown from 'mdi-react/MenuDownIcon'
2212import Menu from 'mdi-react/MenuIcon'
2313import 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'
2616import { MessageTransports } from 'sourcegraph/module/protocol/jsonrpc2/connection'
2717import { TextDocumentDecoration } from 'sourcegraph/module/protocol/plainTypes'
2818import uuid from 'uuid'
2919import { Disposable } from 'vscode-languageserver'
30- import storage , { StorageItems } from '../../browser/storage'
20+ import storage from '../../browser/storage'
21+ import { GQL } from '../../types/gqlschema'
3122import { ExtensionConnectionInfo , onFirstMessage } from '../messaging'
3223import { canFetchForURL } from '../util/context'
3324import { getContext } from './context'
3425import { createAggregateError , isErrorLike } from './errors'
35- import { queryGraphQL } from './graphql'
26+ import { mutateGraphQL , queryGraphQL } from './graphql'
3627import { sendLSPHTTPRequests } from './lsp'
3728import { 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-
179131const 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+ }
0 commit comments