mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 14:11:44 +00:00
web: Update rxjs to v7 (#61122)
A side goal for the web rewrite is to leave the existing code base in a better state than before. I recently [added a hacky workaround](da5ddc99b6/client/web-sveltekit/vite.config.ts (L82-L101)) to make the Svelte version work properly with different rxjs versions. But the whole point of the rewrite is to not have to do these things anymore. So this is my attempt of upgrading rsjx in the main repo to v7.
I worked through the list of breaking changes in the [rxjs documentation](https://rxjs.dev/deprecations/breaking-changes) and fixed TypeScript issues to the best of my abilities.
Most notable changes:
- The custom `combineLatestOrDefault` operator was rewritten to avoid using rxjs internals, and the `.lift` method (side note: the corresponding tests do not cover all expected behavior, but issues had been caught through other tests)
- Where necessary `.toPromise()` was replaced with `lastValueFrom` or `firstValueFrom`. My assumption was that since we don't get runtime errors for the existing code, it's save to assume that the corresponding observables emit at least one value, i.e. `.toPromise()` did not return `undefined`. Only in some places I added a default value where it was easy to deduce what it should be.
- The generic type in `of()` was removed
- The generic type in `concat()` was removed
- `Subject.next` seemed to have allowed `undefined` to be passed even if the subject's types didn't allow that. If the subject's type couldn't be extended to include `undefined` I changed the code to not pass `undefined`.
- The generic type signature of `defaultIfEmpty` changed.
- Where possible I replaced `Subscribable` with `ObservableInput`, but we also have a copy of the old `Subscribable` interface in the `sourcegraph` package, and that makes things more complicated.
- I simplified unnecessary Promise/Observable interop where necessary.
A lot of the complex rxjs logic and usage of changed interfaces, such as `Subscribable`, is in extensions related code, which is not used in the web app anymore, but is still at least imported in the browser extensions code. Most of it is probably not used anymore, which makes the migration somewhat simpler.
This commit is contained in:
parent
c134ad7a1c
commit
c529631483
@ -5,7 +5,7 @@ import '../../config/background.entry'
|
||||
import '../../shared/polyfills'
|
||||
|
||||
import type { Endpoint } from 'comlink'
|
||||
import { combineLatest, merge, type Observable, of, Subject, Subscription, timer } from 'rxjs'
|
||||
import { combineLatest, merge, type Observable, of, Subject, Subscription, timer, lastValueFrom } from 'rxjs'
|
||||
import {
|
||||
bufferCount,
|
||||
filter,
|
||||
@ -238,7 +238,7 @@ async function main(): Promise<void> {
|
||||
variables: V
|
||||
sourcegraphURL?: string
|
||||
}): Promise<GraphQLResult<T>> {
|
||||
return requestGraphQL<T, V>({ request, variables, sourcegraphURL }).toPromise()
|
||||
return lastValueFrom(requestGraphQL<T, V>({ request, variables, sourcegraphURL }))
|
||||
},
|
||||
|
||||
async notifyRepoSyncError({ sourcegraphURL, hasRepoSyncError }, sender: browser.runtime.MessageSender) {
|
||||
|
||||
@ -3,8 +3,8 @@ import assert from 'assert'
|
||||
import { startCase } from 'lodash'
|
||||
import { describe, it } from 'mocha'
|
||||
import type { Target, Page } from 'puppeteer'
|
||||
import { fromEvent } from 'rxjs'
|
||||
import { first, filter, timeout, mergeMap } from 'rxjs/operators'
|
||||
import { firstValueFrom, fromEvent } from 'rxjs'
|
||||
import { filter, timeout, mergeMap } from 'rxjs/operators'
|
||||
|
||||
import { isDefined } from '@sourcegraph/common'
|
||||
import { getConfig } from '@sourcegraph/shared/src/testing/config'
|
||||
@ -141,14 +141,13 @@ describe('Sourcegraph browser extension on github.com', function () {
|
||||
let page: Page = driver.page
|
||||
if (new URL(goToDefinitionURL).hostname !== 'github.com') {
|
||||
;[page] = await Promise.all([
|
||||
fromEvent<Target>(driver.browser, 'targetcreated')
|
||||
.pipe(
|
||||
firstValueFrom(
|
||||
fromEvent<Target>(driver.browser, 'targetcreated').pipe(
|
||||
timeout(5000),
|
||||
mergeMap(target => target.page()),
|
||||
filter(isDefined),
|
||||
first()
|
||||
filter(isDefined)
|
||||
)
|
||||
.toPromise(),
|
||||
),
|
||||
driver.page.click('.test-tooltip-go-to-definition'),
|
||||
])
|
||||
} else {
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { from } from 'rxjs'
|
||||
import { take } from 'rxjs/operators'
|
||||
import { firstValueFrom } from 'rxjs'
|
||||
|
||||
import { type ErrorLike, isErrorLike, isDefined, isNot } from '@sourcegraph/common'
|
||||
import type { Settings } from '@sourcegraph/shared/src/settings/settings'
|
||||
@ -22,7 +21,7 @@ export class SearchCommand {
|
||||
private prev: { query: string; suggestions: browser.omnibox.SuggestResult[] } = { query: '', suggestions: [] }
|
||||
|
||||
public getSuggestions = async (query: string): Promise<browser.omnibox.SuggestResult[]> => {
|
||||
const sourcegraphURL = await observeSourcegraphURL(IS_EXTENSION).pipe(take(1)).toPromise()
|
||||
const sourcegraphURL = await firstValueFrom(observeSourcegraphURL(IS_EXTENSION))
|
||||
return new Promise(resolve => {
|
||||
if (this.prev.query === query) {
|
||||
resolve(this.prev.suggestions)
|
||||
@ -54,7 +53,7 @@ export class SearchCommand {
|
||||
disposition?: 'newForegroundTab' | 'newBackgroundTab' | 'currentTab',
|
||||
currentTabId?: number
|
||||
): Promise<void> => {
|
||||
const sourcegraphURL = await observeSourcegraphURL(IS_EXTENSION).pipe(take(1)).toPromise()
|
||||
const sourcegraphURL = await firstValueFrom(observeSourcegraphURL(IS_EXTENSION))
|
||||
|
||||
const [patternType, caseSensitive] = await this.getDefaultSearchSettings(sourcegraphURL)
|
||||
|
||||
@ -121,7 +120,7 @@ export class SearchCommand {
|
||||
)
|
||||
|
||||
await platformContext.refreshSettings()
|
||||
const settings = (await from(platformContext.settings).pipe(take(1)).toPromise()).final
|
||||
const settings = (await firstValueFrom(platformContext.settings)).final
|
||||
|
||||
if (isDefined(settings) && isNot<ErrorLike | Settings, ErrorLike>(isErrorLike)(settings)) {
|
||||
this.defaultPatternType =
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import * as Sentry from '@sentry/browser'
|
||||
import classNames from 'classnames'
|
||||
import { fromEvent } from 'rxjs'
|
||||
import { fromEvent, lastValueFrom } from 'rxjs'
|
||||
import { filter, map, mapTo, tap } from 'rxjs/operators'
|
||||
import type { Omit } from 'utility-types'
|
||||
|
||||
@ -286,25 +286,25 @@ export const gitlabCodeHost = subtypeOf<CodeHost>()({
|
||||
),
|
||||
|
||||
prepareCodeHost: async requestGraphQL =>
|
||||
requestGraphQL<ResolveRepoNameResult, ResolveRepoNameVariables>({
|
||||
request: gql`
|
||||
query ResolveRepoName($cloneURL: String!) {
|
||||
repository(cloneURL: $cloneURL) {
|
||||
name
|
||||
lastValueFrom(
|
||||
requestGraphQL<ResolveRepoNameResult, ResolveRepoNameVariables>({
|
||||
request: gql`
|
||||
query ResolveRepoName($cloneURL: String!) {
|
||||
repository(cloneURL: $cloneURL) {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
cloneURL: getGitlabRepoURL(),
|
||||
},
|
||||
mightContainPrivateInfo: true,
|
||||
})
|
||||
.pipe(
|
||||
`,
|
||||
variables: {
|
||||
cloneURL: getGitlabRepoURL(),
|
||||
},
|
||||
mightContainPrivateInfo: true,
|
||||
}).pipe(
|
||||
map(dataOrThrowErrors),
|
||||
tap(({ repository }) => {
|
||||
repoNameOnSourcegraph.next(repository?.name ?? '')
|
||||
}),
|
||||
mapTo(true)
|
||||
)
|
||||
.toPromise(),
|
||||
),
|
||||
})
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { from, type Observable, of, throwError } from 'rxjs'
|
||||
import { from, type Observable, of, throwError, lastValueFrom } from 'rxjs'
|
||||
import { fromFetch } from 'rxjs/fetch'
|
||||
import { map, mapTo, switchMap, catchError } from 'rxjs/operators'
|
||||
|
||||
@ -275,9 +275,9 @@ export function getRepoDetailsFromCallsign(
|
||||
* case it fails we query the conduit API.
|
||||
*/
|
||||
export function getSourcegraphURLFromConduit(): Promise<string> {
|
||||
return queryConduitHelper<{ url: string }>('/api/sourcegraph.configuration', {})
|
||||
.pipe(map(({ url }) => url))
|
||||
.toPromise()
|
||||
return lastValueFrom(
|
||||
queryConduitHelper<{ url: string }>('/api/sourcegraph.configuration', {}).pipe(map(({ url }) => url))
|
||||
)
|
||||
}
|
||||
|
||||
const getRepoDetailsFromRepoPHID = memoizeObservable(
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { readFile } from 'mz/fs'
|
||||
import { type Observable, throwError, of } from 'rxjs'
|
||||
import { type Observable, throwError, of, lastValueFrom } from 'rxjs'
|
||||
import { beforeEach, describe, expect, test } from 'vitest'
|
||||
|
||||
import { resetAllMemoizationCaches } from '@sourcegraph/common'
|
||||
@ -145,15 +145,17 @@ const resolveFileInfoFromFixture = async (
|
||||
if (!codeView) {
|
||||
throw new Error(`Code view matching selector ${codeViewSelector} not found`)
|
||||
}
|
||||
return resolver(
|
||||
codeView as HTMLElement,
|
||||
mockRequestGraphQL({
|
||||
...DEFAULT_GRAPHQL_RESPONSES,
|
||||
...graphQLResponseMap,
|
||||
}),
|
||||
mockQueryConduit(conduitResponseMap),
|
||||
new URL(url)
|
||||
).toPromise()
|
||||
return lastValueFrom(
|
||||
resolver(
|
||||
codeView as HTMLElement,
|
||||
mockRequestGraphQL({
|
||||
...DEFAULT_GRAPHQL_RESPONSES,
|
||||
...graphQLResponseMap,
|
||||
}),
|
||||
mockQueryConduit(conduitResponseMap),
|
||||
new URL(url)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
describe('Phabricator file info', () => {
|
||||
|
||||
@ -18,6 +18,7 @@ import {
|
||||
concat,
|
||||
BehaviorSubject,
|
||||
fromEvent,
|
||||
lastValueFrom,
|
||||
} from 'rxjs'
|
||||
import {
|
||||
catchError,
|
||||
@ -643,10 +644,12 @@ const isSafeToContinueCodeIntel = async ({
|
||||
|
||||
rawRepoName = context.rawRepoName
|
||||
|
||||
const isRepoCloned = await resolvePrivateRepo({
|
||||
rawRepoName,
|
||||
requestGraphQL,
|
||||
}).toPromise()
|
||||
const isRepoCloned = await lastValueFrom(
|
||||
resolvePrivateRepo({
|
||||
rawRepoName,
|
||||
requestGraphQL,
|
||||
})
|
||||
)
|
||||
|
||||
return isRepoCloned
|
||||
} catch (error) {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { of } from 'rxjs'
|
||||
import { lastValueFrom, of } from 'rxjs'
|
||||
import { toArray } from 'rxjs/operators'
|
||||
import * as sinon from 'sinon'
|
||||
import type { Omit } from 'utility-types'
|
||||
@ -33,14 +33,14 @@ describe('codeViews', () => {
|
||||
element.className = 'test-code-view'
|
||||
document.body.append(element)
|
||||
const selector = '.test-code-view'
|
||||
const detected = await of([{ addedNodes: [document.body], removedNodes: [] }])
|
||||
.pipe(
|
||||
const detected = await lastValueFrom(
|
||||
of([{ addedNodes: [document.body], removedNodes: [] }]).pipe(
|
||||
trackCodeViews({
|
||||
codeViewResolvers: [toCodeViewResolver(selector, codeViewSpec)],
|
||||
}),
|
||||
toArray()
|
||||
)
|
||||
.toPromise()
|
||||
)
|
||||
expect(detected.map(({ subscriptions, ...rest }) => rest)).toEqual([{ ...codeViewSpec, element }])
|
||||
})
|
||||
it('should detect added code views from resolver', async () => {
|
||||
@ -49,14 +49,14 @@ describe('codeViews', () => {
|
||||
document.body.append(element)
|
||||
const selector = '.test-code-view'
|
||||
const resolveView = sinon.spy((element: HTMLElement) => ({ element, ...codeViewSpec }))
|
||||
const detected = await of([{ addedNodes: [document.body], removedNodes: [] }])
|
||||
.pipe(
|
||||
const detected = await lastValueFrom(
|
||||
of([{ addedNodes: [document.body], removedNodes: [] }]).pipe(
|
||||
trackCodeViews({
|
||||
codeViewResolvers: [{ selector, resolveView }],
|
||||
}),
|
||||
toArray()
|
||||
)
|
||||
.toPromise()
|
||||
)
|
||||
expect(detected.map(({ subscriptions, ...rest }) => rest)).toEqual([{ ...codeViewSpec, element }])
|
||||
sinon.assert.calledOnce(resolveView)
|
||||
sinon.assert.calledWith(resolveView, element)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { noop } from 'lodash'
|
||||
import { from, type Observable, of, Subject, Subscription, NEVER } from 'rxjs'
|
||||
import { from, type Observable, of, Subject, Subscription, NEVER, lastValueFrom } from 'rxjs'
|
||||
import { bufferCount, map, switchMap, toArray } from 'rxjs/operators'
|
||||
import * as sinon from 'sinon'
|
||||
import { afterAll, beforeEach, describe, expect, test } from 'vitest'
|
||||
@ -40,9 +40,9 @@ describe('trackViews()', () => {
|
||||
|
||||
test('detects all views on the page', async () => {
|
||||
const mutations: Observable<MutationRecordLike[]> = of([{ addedNodes: [document.body], removedNodes: [] }])
|
||||
const views = await mutations
|
||||
.pipe(trackViews([{ selector: '.view', resolveView: element => ({ element }) }]), toArray())
|
||||
.toPromise()
|
||||
const views = await lastValueFrom(
|
||||
mutations.pipe(trackViews([{ selector: '.view', resolveView: element => ({ element }) }]), toArray())
|
||||
)
|
||||
expect(views.map(({ element }) => element.id)).toEqual(['view1', 'view2', 'view3'])
|
||||
})
|
||||
|
||||
@ -51,13 +51,13 @@ describe('trackViews()', () => {
|
||||
{ addedNodes: [document.querySelector<HTMLElement>('#view1')!], removedNodes: [] },
|
||||
])
|
||||
expect(
|
||||
await mutations
|
||||
.pipe(
|
||||
await lastValueFrom(
|
||||
mutations.pipe(
|
||||
trackViews([{ selector: '.view', resolveView: element => ({ element }) }]),
|
||||
map(({ element }) => element.id),
|
||||
toArray()
|
||||
)
|
||||
.toPromise()
|
||||
)
|
||||
).toEqual(['view1'])
|
||||
})
|
||||
|
||||
@ -66,13 +66,13 @@ describe('trackViews()', () => {
|
||||
{ addedNodes: [document.querySelector<HTMLElement>('#view1')!], removedNodes: [] },
|
||||
])
|
||||
expect(
|
||||
await mutations
|
||||
.pipe(
|
||||
await lastValueFrom(
|
||||
mutations.pipe(
|
||||
trackViews([{ selector: '.view', resolveView: element => ({ element }) }]),
|
||||
map(({ element }) => element.id),
|
||||
toArray()
|
||||
)
|
||||
.toPromise()
|
||||
)
|
||||
).toEqual(['view1'])
|
||||
})
|
||||
|
||||
@ -82,8 +82,8 @@ describe('trackViews()', () => {
|
||||
selectorTarget.className = 'selector-target'
|
||||
document.querySelector<HTMLElement>('#view1')!.append(selectorTarget)
|
||||
expect(
|
||||
await mutations
|
||||
.pipe(
|
||||
await lastValueFrom(
|
||||
mutations.pipe(
|
||||
trackViews([
|
||||
{
|
||||
selector: '.selector-target',
|
||||
@ -93,15 +93,15 @@ describe('trackViews()', () => {
|
||||
map(({ element }) => element.id),
|
||||
toArray()
|
||||
)
|
||||
.toPromise()
|
||||
)
|
||||
).toEqual(['view1'])
|
||||
})
|
||||
|
||||
test("doesn't emit duplicate views", async () => {
|
||||
const mutations: Observable<MutationRecordLike[]> = of([{ addedNodes: [document.body], removedNodes: [] }])
|
||||
expect(
|
||||
await mutations
|
||||
.pipe(
|
||||
await lastValueFrom(
|
||||
mutations.pipe(
|
||||
trackViews([
|
||||
{
|
||||
selector: '.view',
|
||||
@ -113,7 +113,7 @@ describe('trackViews()', () => {
|
||||
map(({ element }) => element.id),
|
||||
toArray()
|
||||
)
|
||||
.toPromise()
|
||||
)
|
||||
).toEqual(['view1'])
|
||||
})
|
||||
|
||||
@ -190,20 +190,20 @@ describe('trackViews()', () => {
|
||||
[{ addedNodes: [], removedNodes: [document.querySelector<HTMLElement>('#view1')!] }],
|
||||
[{ addedNodes: [], removedNodes: [document.querySelector<HTMLElement>('#view3')!] }],
|
||||
])
|
||||
await mutations
|
||||
.pipe(
|
||||
await lastValueFrom(
|
||||
mutations.pipe(
|
||||
trackViews([{ selector: '.view', resolveView: element => ({ element }) }]),
|
||||
bufferCount(3),
|
||||
switchMap(async ([view1, view2, view3]) => {
|
||||
const v2Removed = sinon.spy(() => undefined)
|
||||
view2.subscriptions.add(v2Removed)
|
||||
const v1Removed = new Promise(resolve => view1.subscriptions.add(resolve))
|
||||
const v3Removed = new Promise(resolve => view3.subscriptions.add(resolve))
|
||||
const v1Removed = new Promise<void>(resolve => view1.subscriptions.add(resolve))
|
||||
const v3Removed = new Promise<void>(resolve => view3.subscriptions.add(resolve))
|
||||
await Promise.all([v1Removed, v3Removed])
|
||||
sinon.assert.notCalled(v2Removed)
|
||||
})
|
||||
)
|
||||
.toPromise()
|
||||
)
|
||||
})
|
||||
|
||||
test('removes all nested views', async () => {
|
||||
@ -211,15 +211,15 @@ describe('trackViews()', () => {
|
||||
[{ addedNodes: [document.body], removedNodes: [] }],
|
||||
[{ addedNodes: [], removedNodes: [document.querySelector<HTMLElement>('#parent')!] }],
|
||||
])
|
||||
await mutations
|
||||
.pipe(
|
||||
await lastValueFrom(
|
||||
mutations.pipe(
|
||||
trackViews([{ selector: '.view', resolveView: element => ({ element }) }]),
|
||||
bufferCount(3),
|
||||
switchMap(views =>
|
||||
Promise.all(views.map(view => new Promise(resolve => view.subscriptions.add(resolve))))
|
||||
Promise.all(views.map(view => new Promise<void>(resolve => view.subscriptions.add(resolve))))
|
||||
)
|
||||
)
|
||||
.toPromise()
|
||||
)
|
||||
})
|
||||
|
||||
test('removes a view without depending on its resolver', async () => {
|
||||
@ -259,7 +259,7 @@ describe('trackViews()', () => {
|
||||
expect(resolver.resolveView(testElement)).toBe(null)
|
||||
|
||||
// Verify that the code view still gets removed.
|
||||
const unsubscribed = new Promise(resolve => view.subscriptions.add(resolve))
|
||||
const unsubscribed = new Promise<void>(resolve => view.subscriptions.add(resolve))
|
||||
mutations.next([{ addedNodes: [], removedNodes: [testElement] }])
|
||||
await unsubscribed
|
||||
})
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import '@sourcegraph/shared/src/polyfills'
|
||||
|
||||
import { fromEvent } from 'rxjs'
|
||||
import { take } from 'rxjs/operators'
|
||||
import { firstValueFrom, fromEvent } from 'rxjs'
|
||||
|
||||
import { hasProperty, logger } from '@sourcegraph/common'
|
||||
import { startExtensionHost } from '@sourcegraph/shared/src/api/extension/extensionHost'
|
||||
@ -24,7 +23,7 @@ const isInitMessage = (value: unknown): value is InitMessage =>
|
||||
*/
|
||||
async function extensionHostMain(): Promise<void> {
|
||||
try {
|
||||
const event = await fromEvent<MessageEvent>(self, 'message').pipe(take(1)).toPromise()
|
||||
const event = await firstValueFrom(fromEvent<MessageEvent>(self, 'message'))
|
||||
if (!isInitMessage(event.data)) {
|
||||
throw new Error('First message event in extension host worker was not a well-formed InitMessage')
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { combineLatest, ReplaySubject } from 'rxjs'
|
||||
import { combineLatest, lastValueFrom, ReplaySubject } from 'rxjs'
|
||||
import { map } from 'rxjs/operators'
|
||||
|
||||
import { asError } from '@sourcegraph/common'
|
||||
@ -77,7 +77,7 @@ export function createPlatformContext(
|
||||
),
|
||||
refreshSettings: async () => {
|
||||
try {
|
||||
const settings = await fetchViewerSettings(requestGraphQL).toPromise()
|
||||
const settings = await lastValueFrom(fetchViewerSettings(requestGraphQL))
|
||||
updatedViewerSettings.next(settings)
|
||||
} catch (error) {
|
||||
if (isHTTPAuthError(error)) {
|
||||
|
||||
@ -51,14 +51,3 @@ export const packageResolutionPlugin = (resolutions: Resolutions): esbuild.Plugi
|
||||
build.onLoad({ filter: new RegExp(''), namespace: 'devnull' }, () => ({ contents: '' }))
|
||||
},
|
||||
})
|
||||
|
||||
export const RXJS_RESOLUTIONS: Resolutions = {
|
||||
// Needed because imports of rxjs/internal/... actually import a different variant of
|
||||
// rxjs in the same package, which leads to observables from combineLatestOrDefault (and
|
||||
// other places that use rxjs/internal/...) not being cross-compatible. See
|
||||
// https://stackoverflow.com/questions/53758889/rxjs-subscribeto-js-observable-check-works-in-chrome-but-fails-in-chrome-incogn.
|
||||
'rxjs/internal/OuterSubscriber': require.resolve('rxjs/_esm5/internal/OuterSubscriber'),
|
||||
'rxjs/internal/util/subscribeToResult': require.resolve('rxjs/_esm5/internal/util/subscribeToResult'),
|
||||
'rxjs/internal/util/subscribeToArray': require.resolve('rxjs/_esm5/internal/util/subscribeToArray'),
|
||||
'rxjs/internal/Observable': require.resolve('rxjs/_esm5/internal/Observable'),
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type * as esbuild from 'esbuild'
|
||||
|
||||
import { packageResolutionPlugin, RXJS_RESOLUTIONS } from './packageResolutionPlugin'
|
||||
import { packageResolutionPlugin } from './packageResolutionPlugin'
|
||||
|
||||
/**
|
||||
* Starts a new esbuild build to create a bundle for a Web Worker.
|
||||
@ -21,7 +21,6 @@ async function buildWorker(
|
||||
plugins: [
|
||||
packageResolutionPlugin({
|
||||
path: require.resolve('path-browserify'),
|
||||
...RXJS_RESOLUTIONS,
|
||||
}),
|
||||
],
|
||||
// Use the minify option as an indicator for running in dev mode.
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { isObject } from 'lodash'
|
||||
import { type Subscribable, type Observable, from } from 'rxjs'
|
||||
import { type Observable, from } from 'rxjs'
|
||||
import { map } from 'rxjs/operators'
|
||||
|
||||
import { isDefined } from '@sourcegraph/common'
|
||||
@ -17,9 +17,9 @@ const isPromiseLike = (value: unknown): value is PromiseLike<unknown> =>
|
||||
* single result, to the same type.
|
||||
*/
|
||||
export const toMaybeLoadingProviderResult = <T>(
|
||||
value: Subscribable<MaybeLoadingResult<T>> | PromiseLike<T>
|
||||
value: Observable<MaybeLoadingResult<T>> | PromiseLike<T>
|
||||
): Observable<MaybeLoadingResult<T>> =>
|
||||
isPromiseLike(value) ? from(value).pipe(map(result => ({ isLoading: false, result }))) : from(value)
|
||||
isPromiseLike(value) ? from(value).pipe(map(result => ({ isLoading: false, result }))) : value
|
||||
|
||||
/**
|
||||
* Returns a function that returns `true` if the given `key` of the object is not `null` or `undefined`.
|
||||
|
||||
@ -2,7 +2,7 @@ import { isEqual } from 'lodash'
|
||||
import { EMPTY, NEVER, of, Subject, Subscription } from 'rxjs'
|
||||
import { delay, distinctUntilChanged, filter, first, map, takeWhile } from 'rxjs/operators'
|
||||
import { TestScheduler } from 'rxjs/testing'
|
||||
import { afterAll, afterEach, beforeAll, describe, it, expect } from 'vitest'
|
||||
import { afterEach, beforeEach, describe, it, expect } from 'vitest'
|
||||
|
||||
import { isDefined } from '@sourcegraph/common'
|
||||
import type { Range } from '@sourcegraph/extension-api-types'
|
||||
@ -27,17 +27,18 @@ import {
|
||||
import { dispatchMouseEventAtPositionImpure } from './testutils/mouse'
|
||||
|
||||
describe('Hoverifier', () => {
|
||||
const dom = new DOM()
|
||||
afterAll(dom.cleanup)
|
||||
|
||||
let dom: DOM
|
||||
let testcases: CodeViewProps[] = []
|
||||
beforeAll(() => {
|
||||
|
||||
beforeEach(() => {
|
||||
dom = new DOM()
|
||||
testcases = dom.createCodeViews()
|
||||
})
|
||||
|
||||
let subscriptions = new Subscription()
|
||||
|
||||
afterEach(() => {
|
||||
dom.cleanup()
|
||||
subscriptions.unsubscribe()
|
||||
subscriptions = new Subscription()
|
||||
})
|
||||
|
||||
@ -11,11 +11,10 @@ import {
|
||||
type Observable,
|
||||
of,
|
||||
Subject,
|
||||
type Subscribable,
|
||||
type SubscribableOrPromise,
|
||||
Subscription,
|
||||
race,
|
||||
type MonoTypeOperatorFunction,
|
||||
ObservableInput,
|
||||
} from 'rxjs'
|
||||
import {
|
||||
catchError,
|
||||
@ -75,7 +74,7 @@ export interface HoverifierOptions<C extends object, D, A> {
|
||||
/**
|
||||
* Emit the HoverOverlay element on this after it was rerendered when its content changed and it needs to be repositioned.
|
||||
*/
|
||||
hoverOverlayRerenders: Subscribable<{
|
||||
hoverOverlayRerenders: ObservableInput<{
|
||||
/**
|
||||
* The HoverOverlay element
|
||||
*/
|
||||
@ -89,13 +88,13 @@ export interface HoverifierOptions<C extends object, D, A> {
|
||||
|
||||
pinOptions?: {
|
||||
/** Emit on this Observable to pin the popover. */
|
||||
pins: Subscribable<void>
|
||||
pins: ObservableInput<void>
|
||||
|
||||
/** * Emit on this Observable when the close button in the HoverOverlay was clicked */
|
||||
closeButtonClicks: Subscribable<void>
|
||||
closeButtonClicks: ObservableInput<void>
|
||||
}
|
||||
|
||||
hoverOverlayElements: Subscribable<HTMLElement | null>
|
||||
hoverOverlayElements: ObservableInput<HTMLElement | null>
|
||||
|
||||
/**
|
||||
* Called to get the data to display in the hover.
|
||||
@ -199,7 +198,7 @@ export interface AdjustPositionProps<C extends object> {
|
||||
*
|
||||
* @template C Extra context for the hovered token.
|
||||
*/
|
||||
export type PositionAdjuster<C extends object> = (props: AdjustPositionProps<C>) => SubscribableOrPromise<Position>
|
||||
export type PositionAdjuster<C extends object> = (props: AdjustPositionProps<C>) => ObservableInput<Position>
|
||||
|
||||
/**
|
||||
* HoverifyOptions that need to be included internally with every event
|
||||
@ -235,13 +234,13 @@ export interface EventOptions<C extends object> {
|
||||
*/
|
||||
export interface HoverifyOptions<C extends object>
|
||||
extends Pick<EventOptions<C>, Exclude<keyof EventOptions<C>, 'codeViewId'>> {
|
||||
positionEvents: Subscribable<PositionEvent>
|
||||
positionEvents: ObservableInput<PositionEvent>
|
||||
|
||||
/**
|
||||
* Emit on this Observable to trigger the overlay on a position in this code view.
|
||||
* This Observable is intended to be used to trigger a Hover after a URL change with a position.
|
||||
*/
|
||||
positionJumps?: Subscribable<PositionJump>
|
||||
positionJumps?: ObservableInput<PositionJump>
|
||||
}
|
||||
|
||||
/**
|
||||
@ -388,7 +387,7 @@ export const MOUSEOVER_DELAY = 50
|
||||
*/
|
||||
export type HoverProvider<C extends object, D> = (
|
||||
position: HoveredToken & C
|
||||
) => Subscribable<MaybeLoadingResult<(HoverAttachment & D) | null>> | PromiseLike<(HoverAttachment & D) | null>
|
||||
) => Observable<MaybeLoadingResult<(HoverAttachment & D) | null>> | PromiseLike<(HoverAttachment & D) | null>
|
||||
|
||||
/**
|
||||
* Function that returns a Subscribable or PromiseLike of the ranges to be highlighted in the document.
|
||||
@ -399,13 +398,13 @@ export type HoverProvider<C extends object, D> = (
|
||||
*/
|
||||
export type DocumentHighlightProvider<C extends object> = (
|
||||
position: HoveredToken & C
|
||||
) => Subscribable<DocumentHighlight[]> | PromiseLike<DocumentHighlight[]>
|
||||
) => ObservableInput<DocumentHighlight[]>
|
||||
|
||||
/**
|
||||
* @template C Extra context for the hovered token.
|
||||
* @template A The type of an action.
|
||||
*/
|
||||
export type ActionsProvider<C extends object, A> = (position: HoveredToken & C) => SubscribableOrPromise<A[] | null>
|
||||
export type ActionsProvider<C extends object, A> = (position: HoveredToken & C) => ObservableInput<A[] | null>
|
||||
|
||||
/**
|
||||
* Function responsible for resolving the position of a hovered token
|
||||
@ -1050,7 +1049,9 @@ export function createHoverifier<C extends object, D, A>({
|
||||
}
|
||||
|
||||
// Pin on request.
|
||||
subscription.add(pinOptions?.pins.subscribe(() => container.update({ pinned: true })))
|
||||
if (pinOptions) {
|
||||
subscription.add(from(pinOptions.pins).subscribe(() => container.update({ pinned: true })))
|
||||
}
|
||||
|
||||
// Unpin on close, ESC, or click.
|
||||
subscription.add(
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { from, fromEvent, merge, type Observable, type Subscribable } from 'rxjs'
|
||||
import { from, fromEvent, merge, type Observable } from 'rxjs'
|
||||
import { filter, map, switchMap, tap } from 'rxjs/operators'
|
||||
|
||||
import { convertCodeElementIdempotent, type DOMFunctions, type HoveredToken, locateTarget } from './tokenPosition'
|
||||
@ -28,7 +28,7 @@ export interface PositionEvent {
|
||||
|
||||
export const findPositionsFromEvents =
|
||||
({ domFunctions, tokenize = true }: { domFunctions: DOMFunctions; tokenize?: boolean }) =>
|
||||
(elements: Subscribable<HTMLElement>): Observable<PositionEvent> =>
|
||||
(elements: Observable<HTMLElement>): Observable<PositionEvent> =>
|
||||
merge(
|
||||
from(elements).pipe(
|
||||
switchMap(element =>
|
||||
|
||||
@ -2,7 +2,6 @@ import { of } from 'rxjs'
|
||||
import { delay } from 'rxjs/operators'
|
||||
|
||||
import type { ActionsProvider, HoverProvider, DocumentHighlightProvider } from '../hoverifier'
|
||||
import type { MaybeLoadingResult } from '../loading'
|
||||
import type { HoverAttachment, DocumentHighlight } from '../types'
|
||||
|
||||
/**
|
||||
@ -41,10 +40,7 @@ export function createStubHoverProvider(
|
||||
hover: Partial<HoverAttachment> = {},
|
||||
delayTime?: number
|
||||
): HoverProvider<{}, {}> {
|
||||
return () =>
|
||||
of<MaybeLoadingResult<{}>>({ isLoading: false, result: createHoverAttachment(hover) }).pipe(
|
||||
delay(delayTime ?? 0)
|
||||
)
|
||||
return () => of({ isLoading: false, result: createHoverAttachment(hover) }).pipe(delay(delayTime ?? 0))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -57,7 +53,7 @@ export function createStubDocumentHighlightProvider(
|
||||
documentHighlights: Partial<DocumentHighlight>[] = [],
|
||||
delayTime?: number
|
||||
): DocumentHighlightProvider<{}> {
|
||||
return () => of<DocumentHighlight[]>(documentHighlights.map(createDocumentHighlight)).pipe(delay(delayTime ?? 0))
|
||||
return () => of(documentHighlights.map(createDocumentHighlight)).pipe(delay(delayTime ?? 0))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -46,6 +46,7 @@ ts_project(
|
||||
"src/util/path.ts",
|
||||
"src/util/rxjs/asObservable.ts",
|
||||
"src/util/rxjs/combineLatestOrDefault.ts",
|
||||
"src/util/rxjs/fromSubscribable.ts",
|
||||
"src/util/rxjs/index.ts",
|
||||
"src/util/rxjs/memoizeObservable.ts",
|
||||
"src/util/rxjs/repeatUntil.ts",
|
||||
@ -71,6 +72,7 @@ ts_project(
|
||||
"//:node_modules/marked",
|
||||
"//:node_modules/react-router-dom",
|
||||
"//:node_modules/rxjs",
|
||||
"//:node_modules/sourcegraph",
|
||||
"//:node_modules/utility-types",
|
||||
],
|
||||
)
|
||||
|
||||
@ -1,18 +1,4 @@
|
||||
/* eslint rxjs/no-internal: warn */
|
||||
import {
|
||||
asapScheduler,
|
||||
type ObservableInput,
|
||||
of,
|
||||
type Operator,
|
||||
type PartialObserver,
|
||||
type Subscriber,
|
||||
type TeardownLogic,
|
||||
zip,
|
||||
} from 'rxjs'
|
||||
import { Observable } from 'rxjs/internal/Observable'
|
||||
import { OuterSubscriber } from 'rxjs/internal/OuterSubscriber'
|
||||
import { subscribeToArray } from 'rxjs/internal/util/subscribeToArray'
|
||||
import { subscribeToResult } from 'rxjs/internal/util/subscribeToResult'
|
||||
import { asapScheduler, type ObservableInput, Observable, of, zip, from, Subscription } from 'rxjs'
|
||||
|
||||
/**
|
||||
* Like {@link combineLatest}, except that it does not wait for all Observables to emit before emitting an initial
|
||||
@ -51,74 +37,68 @@ export function combineLatestOrDefault<T>(observables: ObservableInput<T>[], def
|
||||
return zip(...observables)
|
||||
}
|
||||
default: {
|
||||
return new Observable<T[]>(subscribeToArray(observables)).lift(new CombineLatestOperator(defaultValue))
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Observable<T[]>(subscriber => {
|
||||
// The array of the most recent values from each input Observable.
|
||||
// If a source Observable has not yet emitted a value, it will be represented by the
|
||||
// defaultValue (if provided) or not at all (if not provided).
|
||||
const values: T[] = defaultValue !== undefined ? observables.map(() => defaultValue) : []
|
||||
|
||||
class CombineLatestOperator<T> implements Operator<T, T[]> {
|
||||
constructor(private defaultValue?: T) {}
|
||||
// Whether the emission of the values array has been scheduled
|
||||
let scheduled = false
|
||||
let scheduledWork: Subscription | undefined
|
||||
// The number of source Observables that have not yet completed
|
||||
// (so that we know when to complete the output Observable)
|
||||
let activeObservables = observables.length
|
||||
|
||||
public call(subscriber: Subscriber<T[]>, source: any): TeardownLogic {
|
||||
return source.subscribe(new CombineLatestSubscriber(subscriber, this.defaultValue))
|
||||
}
|
||||
}
|
||||
// When everything is done, clean up the values array
|
||||
subscriber.add(() => {
|
||||
values.length = 0
|
||||
})
|
||||
|
||||
class CombineLatestSubscriber<T> extends OuterSubscriber<T, T[]> {
|
||||
private activeObservables = 0
|
||||
private values: any[] = []
|
||||
private observables: Observable<any>[] = []
|
||||
private scheduled = false
|
||||
|
||||
constructor(observer: PartialObserver<T[]>, private defaultValue?: T) {
|
||||
super(observer)
|
||||
}
|
||||
|
||||
protected _next(observable: any): void {
|
||||
if (this.defaultValue !== undefined) {
|
||||
this.values.push(this.defaultValue)
|
||||
}
|
||||
this.observables.push(observable)
|
||||
}
|
||||
|
||||
protected _complete(): void {
|
||||
this.activeObservables = this.observables.length
|
||||
for (let index = 0; index < this.observables.length; index++) {
|
||||
this.add(subscribeToResult(this, this.observables[index], this.observables[index], index))
|
||||
}
|
||||
}
|
||||
|
||||
public notifyComplete(): void {
|
||||
this.activeObservables--
|
||||
if (this.activeObservables === 0 && this.destination.complete) {
|
||||
this.destination.complete()
|
||||
}
|
||||
}
|
||||
|
||||
public notifyNext(_outerValue: T, innerValue: T[], outerIndex: number): void {
|
||||
const values = this.values
|
||||
values[outerIndex] = innerValue
|
||||
|
||||
if (this.activeObservables === 1) {
|
||||
// Only 1 observable is active, so no need to buffer.
|
||||
//
|
||||
// This makes it possible to use RxJS's `of` in tests without specifying an explicit scheduler.
|
||||
if (this.destination.next) {
|
||||
this.destination.next(this.values.slice())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Buffer all next values that are emitted at the same time into one emission.
|
||||
//
|
||||
// This makes tests (using expectObservable) easier to write.
|
||||
if (!this.scheduled) {
|
||||
this.scheduled = true
|
||||
asapScheduler.schedule(() => {
|
||||
if (this.scheduled && this.destination.next) {
|
||||
this.destination.next(this.values.slice())
|
||||
// Subscribe to each source Observable. The index of the source Observable is used to
|
||||
// keep track of the most recent value from that Observable in the values array.
|
||||
for (let index = 0; index < observables.length; index++) {
|
||||
subscriber.add(
|
||||
from(observables[index]).subscribe({
|
||||
next: value => {
|
||||
values[index] = value
|
||||
if (activeObservables === 1) {
|
||||
// If only one source Observable is active, emit the values array immediately
|
||||
// Abort any scheduled emission
|
||||
scheduledWork?.unsubscribe()
|
||||
scheduled = false
|
||||
subscriber.next(values.slice())
|
||||
} else if (!scheduled) {
|
||||
scheduled = true
|
||||
// Use asapScheduler to emit the values array, so that all
|
||||
// next values that are emitted at the same time are emitted together.
|
||||
// This makes tests (using expectObservable) easier to write.
|
||||
scheduledWork = asapScheduler.schedule(() => {
|
||||
if (!subscriber.closed) {
|
||||
subscriber.next(values.slice())
|
||||
scheduled = false
|
||||
if (activeObservables === 0) {
|
||||
subscriber.complete()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
error: error => subscriber.error(error),
|
||||
complete: () => {
|
||||
activeObservables--
|
||||
if (activeObservables === 0 && !scheduled) {
|
||||
subscriber.complete()
|
||||
}
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// When everything is done, clean up the values array
|
||||
return () => {
|
||||
values.length = 0
|
||||
}
|
||||
this.scheduled = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
14
client/common/src/util/rxjs/fromSubscribable.ts
Normal file
14
client/common/src/util/rxjs/fromSubscribable.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Observable, isObservable } from 'rxjs'
|
||||
import { type Subscribable } from 'sourcegraph'
|
||||
|
||||
/**
|
||||
* Converts a Sourcegraph {@link Subscribable} to an {@link Observable}.
|
||||
*/
|
||||
export function fromSubscribable<T>(value: Subscribable<T>): Observable<T> {
|
||||
if (isObservable(value)) {
|
||||
// type casting should be fine since we already know that
|
||||
// value is at least a Subscribable<T>
|
||||
return value as Observable<T>
|
||||
}
|
||||
return new Observable<T>(subscriber => value.subscribe(subscriber))
|
||||
}
|
||||
@ -2,3 +2,4 @@ export * from './asObservable'
|
||||
export * from './combineLatestOrDefault'
|
||||
export * from './memoizeObservable'
|
||||
export * from './repeatUntil'
|
||||
export * from './fromSubscribable'
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { lastValueFrom } from 'rxjs'
|
||||
|
||||
import { gql, requestGraphQLCommon } from '@sourcegraph/http-client'
|
||||
import type { AuthenticatedUser } from '@sourcegraph/shared/src/auth'
|
||||
import type { CurrentAuthStateResult, CurrentAuthStateVariables } from '@sourcegraph/shared/src/graphql-operations'
|
||||
@ -57,18 +59,20 @@ export async function getSiteVersionAndAuthenticatedUser(
|
||||
return { site: null, currentUser: null }
|
||||
}
|
||||
|
||||
const result = await requestGraphQLCommon<SiteVersionAndCurrentAuthStateResult, CurrentAuthStateVariables>({
|
||||
request: siteVersionAndUserQuery,
|
||||
variables: {},
|
||||
baseUrl: instanceURL,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'X-Sourcegraph-Should-Trace': new URLSearchParams(window.location.search).get('trace') || 'false',
|
||||
...(accessToken && { Authorization: `token ${accessToken}` }),
|
||||
...customRequestHeaders,
|
||||
},
|
||||
}).toPromise()
|
||||
const result = await lastValueFrom(
|
||||
requestGraphQLCommon<SiteVersionAndCurrentAuthStateResult, CurrentAuthStateVariables>({
|
||||
request: siteVersionAndUserQuery,
|
||||
variables: {},
|
||||
baseUrl: instanceURL,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'X-Sourcegraph-Should-Trace': new URLSearchParams(window.location.search).get('trace') || 'false',
|
||||
...(accessToken && { Authorization: `token ${accessToken}` }),
|
||||
...customRequestHeaders,
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
return result.data ?? { site: null, currentUser: null }
|
||||
}
|
||||
|
||||
@ -82,12 +82,12 @@ export interface ActionsNavItemsProps
|
||||
export const ActionsNavItems: React.FunctionComponent<React.PropsWithChildren<ActionsNavItemsProps>> = props => {
|
||||
const { scope, extraContext, extensionsController, menu, wrapInList, transformContributions = identity } = props
|
||||
|
||||
const scopeChanges = useMemo(() => new ReplaySubject<ContributionScope>(1), [])
|
||||
const scopeChanges = useMemo(() => new ReplaySubject<ContributionScope | undefined>(1), [])
|
||||
useDeepCompareEffectNoCheck(() => {
|
||||
scopeChanges.next(scope)
|
||||
}, [scope])
|
||||
|
||||
const extraContextChanges = useMemo(() => new ReplaySubject<Context<unknown>>(1), [])
|
||||
const extraContextChanges = useMemo(() => new ReplaySubject<Context<unknown> | undefined>(1), [])
|
||||
useDeepCompareEffectNoCheck(() => {
|
||||
extraContextChanges.next(extraContext)
|
||||
}, [extraContext])
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { type Remote, proxyMarker, releaseProxy, type ProxyMethods, type ProxyOrClone } from 'comlink'
|
||||
import { noop } from 'lodash'
|
||||
import { from, type Observable, observable as symbolObservable, Subscription, type Subscribable } from 'rxjs'
|
||||
import { from, Observable, Subscription } from 'rxjs'
|
||||
import { mergeMap, finalize } from 'rxjs/operators'
|
||||
|
||||
import { asError, logger } from '@sourcegraph/common'
|
||||
@ -59,37 +58,18 @@ export const wrapRemoteObservable = <T>(
|
||||
const observable = from(
|
||||
isPromiseLike(proxyOrProxyPromise) ? proxyOrProxyPromise : Promise.resolve(proxyOrProxyPromise)
|
||||
).pipe(
|
||||
mergeMap((proxySubscribable): Subscribable<ProxyOrClone<T>> => {
|
||||
mergeMap((proxySubscribable): Observable<ProxyOrClone<T>> => {
|
||||
proxySubscription.add(new ProxySubscription(proxySubscribable))
|
||||
return {
|
||||
// Needed for Rx type check
|
||||
[symbolObservable](): Subscribable<ProxyOrClone<T>> {
|
||||
return this
|
||||
},
|
||||
subscribe(...args: any[]): Subscription {
|
||||
// Always subscribe with an object because the other side
|
||||
// is unable to tell if a Proxy is a function or an observer object
|
||||
// (they always appear as functions)
|
||||
let proxyObserver: Parameters<typeof proxySubscribable['subscribe']>[0]
|
||||
if (typeof args[0] === 'function') {
|
||||
proxyObserver = {
|
||||
[proxyMarker]: true,
|
||||
next: args[0] || noop,
|
||||
error: args[1] ? error => args[1](asError(error)) : noop,
|
||||
complete: args[2] || noop,
|
||||
}
|
||||
} else {
|
||||
const partialObserver = args[0] || {}
|
||||
proxyObserver = {
|
||||
[proxyMarker]: true,
|
||||
next: partialObserver.next ? value => partialObserver.next(value) : noop,
|
||||
error: partialObserver.error ? error => partialObserver.error(asError(error)) : noop,
|
||||
complete: partialObserver.complete ? () => partialObserver.complete() : noop,
|
||||
}
|
||||
}
|
||||
return syncRemoteSubscription(proxySubscribable.subscribe(proxyObserver))
|
||||
},
|
||||
}
|
||||
return new Observable(subscriber => {
|
||||
const proxyObserver: Parameters<typeof proxySubscribable['subscribe']>[0] = {
|
||||
[proxyMarker]: true,
|
||||
// @ts-expect-error - this was previously typed as any
|
||||
next: value => subscriber.next(value),
|
||||
error: error => subscriber.error(asError(error)),
|
||||
complete: () => subscriber.complete(),
|
||||
}
|
||||
return syncRemoteSubscription(proxySubscribable.subscribe(proxyObserver))
|
||||
})
|
||||
})
|
||||
)
|
||||
return Object.assign(observable, { proxySubscription })
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import * as comlink from 'comlink'
|
||||
import { from, Subscription, type Unsubscribable } from 'rxjs'
|
||||
import { first } from 'rxjs/operators'
|
||||
import { firstValueFrom, Subscription, type Unsubscribable } from 'rxjs'
|
||||
|
||||
import { logger } from '@sourcegraph/common'
|
||||
|
||||
@ -42,7 +41,7 @@ export async function createExtensionHostClientConnection(
|
||||
/** Proxy to the exposed extension host API */
|
||||
const initializeExtensionHost = comlink.wrap<ExtensionHostAPIFactory>(endpoints.proxy)
|
||||
|
||||
const initialSettings = await from(platformContext.settings).pipe(first()).toPromise()
|
||||
const initialSettings = await firstValueFrom(platformContext.settings)
|
||||
const proxy = await initializeExtensionHost({
|
||||
...initData,
|
||||
// TODO what to do in error case?
|
||||
|
||||
@ -19,7 +19,7 @@ describe('MainThreadAPI', () => {
|
||||
|
||||
describe('graphQL', () => {
|
||||
test('PlatformContext#requestGraphQL is called with the correct arguments', async () => {
|
||||
const requestGraphQL = sinon.spy(_options => EMPTY)
|
||||
const requestGraphQL = sinon.spy(_options => of({ data: null, errors: [] }))
|
||||
|
||||
const platformContext: Pick<
|
||||
PlatformContext,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { type Remote, proxy } from 'comlink'
|
||||
import { type Unsubscribable, Subscription, from, of } from 'rxjs'
|
||||
import { type Unsubscribable, Subscription, from, of, lastValueFrom } from 'rxjs'
|
||||
import { publishReplay, refCount, switchMap } from 'rxjs/operators'
|
||||
|
||||
import { logger } from '@sourcegraph/common'
|
||||
@ -101,13 +101,13 @@ export const initMainThreadAPI = (
|
||||
const api: MainThreadAPI = {
|
||||
applySettingsEdit: edit => updateSettings(platformContext, edit),
|
||||
requestGraphQL: (request, variables) =>
|
||||
platformContext
|
||||
.requestGraphQL({
|
||||
lastValueFrom(
|
||||
platformContext.requestGraphQL({
|
||||
request,
|
||||
variables,
|
||||
mightContainPrivateInfo: true,
|
||||
})
|
||||
.toPromise(),
|
||||
),
|
||||
// Commands
|
||||
executeCommand: (command, args) => executeCommand({ command, args }),
|
||||
registerCommand: (command, run) => {
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { from } from 'rxjs'
|
||||
import { first } from 'rxjs/operators'
|
||||
import { firstValueFrom } from 'rxjs'
|
||||
|
||||
import type { KeyPath } from '@sourcegraph/client-api'
|
||||
|
||||
@ -28,7 +27,7 @@ export async function updateSettings(
|
||||
edit: SettingsEdit
|
||||
): Promise<void> {
|
||||
const { settings: data, updateSettings: update } = platformContext
|
||||
const settings = await from(data).pipe(first()).toPromise()
|
||||
const settings = await firstValueFrom(data)
|
||||
if (!isSettingsValid(settings)) {
|
||||
throw new Error('invalid settings (internal error)')
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import type { Remote, ProxyMarked } from 'comlink'
|
||||
import type { Unsubscribable } from 'rxjs'
|
||||
import type { DocumentHighlight } from 'sourcegraph'
|
||||
|
||||
import type {
|
||||
Contributions,
|
||||
@ -13,7 +12,7 @@ import type { MaybeLoadingResult } from '@sourcegraph/codeintellify'
|
||||
import type * as clientType from '@sourcegraph/extension-api-types'
|
||||
import type { GraphQLResult } from '@sourcegraph/http-client'
|
||||
|
||||
import type { ReferenceContext } from '../codeintel/legacy-extensions/api'
|
||||
import type { DocumentHighlight, ReferenceContext } from '../codeintel/legacy-extensions/api'
|
||||
import type { Occurrence } from '../codeintel/scip'
|
||||
import type { ConfiguredExtension } from '../extensions/extension'
|
||||
import type { SettingsCascade } from '../settings/settings'
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { type Remote, type ProxyMarked, proxy, proxyMarker, type UnproxyOrClone } from 'comlink'
|
||||
import { identity } from 'lodash'
|
||||
import { from, isObservable, type Observable, type Observer, of, type Subscribable, type Unsubscribable } from 'rxjs'
|
||||
import { from, isObservable, type Observable, type Observer, of, type Unsubscribable, ObservableInput } from 'rxjs'
|
||||
import { map } from 'rxjs/operators'
|
||||
import type { ProviderResult } from 'sourcegraph'
|
||||
import type { ProviderResult, Subscribable } from 'sourcegraph'
|
||||
|
||||
import { fromSubscribable } from '@sourcegraph/common'
|
||||
|
||||
import { isAsyncIterable, isPromiseLike, isSubscribable, observableFromAsyncIterable } from '../../util'
|
||||
|
||||
@ -50,8 +52,10 @@ export function providerResultToObservable<T, R = T>(
|
||||
mapFunc: (value: T | undefined | null) => R = identity
|
||||
): Observable<R> {
|
||||
let observable: Observable<R>
|
||||
if (result && (isPromiseLike(result) || isObservable<T>(result) || isSubscribable(result))) {
|
||||
observable = from(result).pipe(map(mapFunc))
|
||||
if (result && (isPromiseLike(result) || isObservable(result))) {
|
||||
observable = from(result as ObservableInput<T>).pipe(map(mapFunc))
|
||||
} else if (isSubscribable(result)) {
|
||||
observable = fromSubscribable(result as Subscribable<T>).pipe(map(mapFunc))
|
||||
} else if (isAsyncIterable(result)) {
|
||||
observable = observableFromAsyncIterable(result).pipe(map(mapFunc))
|
||||
} else {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { proxy } from 'comlink'
|
||||
import { castArray, isEqual } from 'lodash'
|
||||
import { combineLatest, concat, type Observable, of, type Subscribable } from 'rxjs'
|
||||
import { combineLatest, concat, type Observable, of } from 'rxjs'
|
||||
import { catchError, defaultIfEmpty, distinctUntilChanged, map, switchMap } from 'rxjs/operators'
|
||||
import type { ProviderResult } from 'sourcegraph'
|
||||
|
||||
@ -319,7 +319,7 @@ export function createExtensionHostAPI(state: ExtensionHostState): FlatExtension
|
||||
})
|
||||
),
|
||||
state.settings,
|
||||
state.context as Subscribable<Context<unknown>>,
|
||||
state.context as Observable<Context<unknown>>,
|
||||
]).pipe(
|
||||
map(([multiContributions, activeEditor, settings, context]) => {
|
||||
// Merge in extra context.
|
||||
@ -431,7 +431,7 @@ export function callProviders<TRegisteredProvider, TProviderResult, TMergedResul
|
||||
concat(
|
||||
[LOADING],
|
||||
providerResultToObservable(safeInvokeProvider(provider)).pipe(
|
||||
defaultIfEmpty<typeof LOADING | TProviderResult | null | undefined>(null),
|
||||
defaultIfEmpty(null),
|
||||
catchError(error => {
|
||||
logError(error)
|
||||
return [null]
|
||||
@ -443,7 +443,7 @@ export function callProviders<TRegisteredProvider, TProviderResult, TMergedResul
|
||||
)
|
||||
)
|
||||
.pipe(
|
||||
defaultIfEmpty<(typeof LOADING | TProviderResult | null | undefined)[]>([]),
|
||||
defaultIfEmpty([]),
|
||||
map(results => ({
|
||||
isLoading: results.some(hover => hover === LOADING),
|
||||
result: mergeResult(results),
|
||||
|
||||
@ -61,6 +61,7 @@ describe('getDocumentHighlights from ExtensionHost API, it aims to have more e2e
|
||||
position: { line: 1, character: 2 },
|
||||
textDocument: { uri: typescriptFileUri },
|
||||
})
|
||||
// @ts-expect-error - Unclear how to consolidate the different versions of the DocumentHighlight types
|
||||
.subscribe(observe(value => results.push(value)))
|
||||
|
||||
// first provider results
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { from } from 'rxjs'
|
||||
import { lastValueFrom } from 'rxjs'
|
||||
import { distinctUntilChanged, switchMap, take, toArray } from 'rxjs/operators'
|
||||
import { describe, test } from 'vitest'
|
||||
|
||||
import { fromSubscribable } from '@sourcegraph/common'
|
||||
import { Selection } from '@sourcegraph/extension-api-classes'
|
||||
|
||||
import { assertToJSON, integrationTestContext } from '../../testing/testHelpers'
|
||||
@ -11,14 +12,16 @@ describe('CodeEditor (integration)', () => {
|
||||
test('observe changes', async () => {
|
||||
const { extensionAPI, extensionHostAPI } = await integrationTestContext()
|
||||
|
||||
const values = from(extensionAPI.app.activeWindow!.activeViewComponentChanges)
|
||||
.pipe(
|
||||
switchMap(viewer => (viewer && viewer.type === 'CodeEditor' ? viewer.selectionsChanges : [])),
|
||||
const values = lastValueFrom(
|
||||
fromSubscribable(extensionAPI.app.activeWindow!.activeViewComponentChanges).pipe(
|
||||
switchMap(viewer =>
|
||||
viewer && viewer.type === 'CodeEditor' ? fromSubscribable(viewer.selectionsChanges) : []
|
||||
),
|
||||
distinctUntilChanged(),
|
||||
take(3),
|
||||
toArray()
|
||||
)
|
||||
.toPromise()
|
||||
)
|
||||
|
||||
await extensionHostAPI.setEditorSelections({ viewerId: 'viewer#0' }, [new Selection(1, 2, 3, 4)])
|
||||
await extensionHostAPI.setEditorSelections({ viewerId: 'viewer#0' }, [])
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { describe, expect, test } from 'vitest'
|
||||
|
||||
import { fromSubscribable } from '@sourcegraph/common'
|
||||
|
||||
import type { TextDocument } from '../../codeintel/legacy-extensions/api'
|
||||
import { assertToJSON, collectSubscribableValues, integrationTestContext } from '../../testing/testHelpers'
|
||||
|
||||
@ -28,7 +30,7 @@ describe('Documents (integration)', () => {
|
||||
test('fires when a text document is opened', async () => {
|
||||
const { extensionAPI, extensionHostAPI } = await integrationTestContext()
|
||||
|
||||
const values = collectSubscribableValues(extensionAPI.workspace.openedTextDocuments)
|
||||
const values = collectSubscribableValues(fromSubscribable(extensionAPI.workspace.openedTextDocuments))
|
||||
expect(values).toEqual([] as TextDocument[])
|
||||
|
||||
await extensionHostAPI.addTextDocumentIfNotExists({ uri: 'file:///f2', languageId: 'l2', text: 't2' })
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { describe, expect, test } from 'vitest'
|
||||
|
||||
import { fromSubscribable } from '@sourcegraph/common'
|
||||
|
||||
import type { WorkspaceRoot } from '../../codeintel/legacy-extensions/api'
|
||||
import { collectSubscribableValues, integrationTestContext } from '../../testing/testHelpers'
|
||||
|
||||
@ -34,7 +36,7 @@ describe('Workspace roots (integration)', () => {
|
||||
test('fires when a root is added or removed', async () => {
|
||||
const { extensionAPI, extensionHostAPI } = await integrationTestContext()
|
||||
|
||||
const values = collectSubscribableValues(extensionAPI.workspace.rootChanges)
|
||||
const values = collectSubscribableValues(fromSubscribable(extensionAPI.workspace.rootChanges))
|
||||
expect(values).toEqual([] as void[])
|
||||
|
||||
await extensionHostAPI.addWorkspaceRoot({
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { from } from 'rxjs'
|
||||
import { distinctUntilChanged, filter, switchMap } from 'rxjs/operators'
|
||||
import { describe, test } from 'vitest'
|
||||
|
||||
import { isDefined, isTaggedUnionMember } from '@sourcegraph/common'
|
||||
import { fromSubscribable, isDefined, isTaggedUnionMember } from '@sourcegraph/common'
|
||||
|
||||
import { assertToJSON, collectSubscribableValues, integrationTestContext } from '../../testing/testHelpers'
|
||||
|
||||
@ -10,13 +9,13 @@ describe('Selections (integration)', () => {
|
||||
describe('editor.selectionsChanged', () => {
|
||||
test('reflects changes to the current selections', async () => {
|
||||
const { extensionAPI, extensionHostAPI } = await integrationTestContext()
|
||||
const selectionChanges = from(extensionAPI.app.activeWindowChanges).pipe(
|
||||
const selectionChanges = fromSubscribable(extensionAPI.app.activeWindowChanges).pipe(
|
||||
filter(isDefined),
|
||||
switchMap(window => window.activeViewComponentChanges),
|
||||
switchMap(window => fromSubscribable(window.activeViewComponentChanges)),
|
||||
filter(isDefined),
|
||||
filter(isTaggedUnionMember('type', 'CodeEditor' as const)),
|
||||
distinctUntilChanged(),
|
||||
switchMap(editor => editor.selectionsChanges)
|
||||
switchMap(editor => fromSubscribable(editor.selectionsChanges))
|
||||
)
|
||||
const selectionValues = collectSubscribableValues(selectionChanges)
|
||||
const testValues = [
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { pick } from 'lodash'
|
||||
import { from, of } from 'rxjs'
|
||||
import { lastValueFrom, of } from 'rxjs'
|
||||
import { switchMap, take, toArray } from 'rxjs/operators'
|
||||
import type { ViewComponent, Window } from 'sourcegraph'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
|
||||
import { fromSubscribable } from '@sourcegraph/common'
|
||||
|
||||
import { assertToJSON, integrationTestContext } from '../../testing/testHelpers'
|
||||
import type { TextDocumentData } from '../viewerTypes'
|
||||
|
||||
@ -159,13 +161,15 @@ describe('Windows (integration)', () => {
|
||||
viewers: [],
|
||||
})
|
||||
|
||||
const viewers = from(extensionAPI.app.activeWindowChanges)
|
||||
.pipe(
|
||||
switchMap(activeWindow => (activeWindow ? activeWindow.activeViewComponentChanges : of(null))),
|
||||
const viewers = lastValueFrom(
|
||||
fromSubscribable(extensionAPI.app.activeWindowChanges).pipe(
|
||||
switchMap(activeWindow =>
|
||||
activeWindow ? fromSubscribable(activeWindow.activeViewComponentChanges) : of(null)
|
||||
),
|
||||
take(4),
|
||||
toArray()
|
||||
)
|
||||
.toPromise()
|
||||
)
|
||||
|
||||
await extensionHostAPI.addTextDocumentIfNotExists({ uri: 'foo', languageId: 'l1', text: 't1' })
|
||||
await extensionHostAPI.addTextDocumentIfNotExists({ uri: 'bar', languageId: 'l2', text: 't2' })
|
||||
|
||||
@ -6,14 +6,8 @@ import {
|
||||
type Remote,
|
||||
proxyMarker,
|
||||
} from 'comlink'
|
||||
import {
|
||||
type Unsubscribable,
|
||||
type Subscribable,
|
||||
Observable,
|
||||
type Observer,
|
||||
type PartialObserver,
|
||||
Subscription,
|
||||
} from 'rxjs'
|
||||
import { type Unsubscribable, Observable, type Observer, type PartialObserver, Subscription } from 'rxjs'
|
||||
import { Subscribable } from 'sourcegraph'
|
||||
|
||||
import { hasProperty, AbortError } from '@sourcegraph/common'
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { castArray } from 'lodash'
|
||||
import { from, type Observable, of } from 'rxjs'
|
||||
import { from, of, lastValueFrom } from 'rxjs'
|
||||
import { defaultIfEmpty, map } from 'rxjs/operators'
|
||||
|
||||
import {
|
||||
@ -42,7 +42,7 @@ export interface CodeIntelAPI {
|
||||
scipParameters?: ScipParameters
|
||||
): Promise<clientType.Location[]>
|
||||
getImplementations(parameters: TextDocumentPositionParameters): Promise<clientType.Location[]>
|
||||
getHover(textParameters: TextDocumentPositionParameters): Promise<HoverMerged | null | undefined>
|
||||
getHover(textParameters: TextDocumentPositionParameters): Promise<HoverMerged | null>
|
||||
getDocumentHighlights(textParameters: TextDocumentPositionParameters): Promise<DocumentHighlight[]>
|
||||
}
|
||||
|
||||
@ -57,9 +57,9 @@ export async function getOrCreateCodeIntelAPI(context: PlatformContext): Promise
|
||||
return codeIntelAPI
|
||||
}
|
||||
|
||||
return new Promise<CodeIntelAPI>((resolve, reject) => {
|
||||
context.settings.subscribe(settingsCascade => {
|
||||
try {
|
||||
return lastValueFrom(
|
||||
context.settings.pipe(
|
||||
map(settingsCascade => {
|
||||
if (!isSettingsValid(settingsCascade)) {
|
||||
throw new Error('Settings are not valid')
|
||||
}
|
||||
@ -68,28 +68,27 @@ export async function getOrCreateCodeIntelAPI(context: PlatformContext): Promise
|
||||
telemetryService: context.telemetryService,
|
||||
settings: newSettingsGetter(settingsCascade),
|
||||
})
|
||||
resolve(codeIntelAPI)
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
})
|
||||
return codeIntelAPI
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
class DefaultCodeIntelAPI implements CodeIntelAPI {
|
||||
private locationResult(
|
||||
locations: sourcegraph.ProviderResult<sourcegraph.Definition>
|
||||
): Promise<clientType.Location[]> {
|
||||
return locations
|
||||
.pipe(
|
||||
defaultIfEmpty(),
|
||||
return lastValueFrom(
|
||||
locations.pipe(
|
||||
defaultIfEmpty(undefined),
|
||||
map(result =>
|
||||
castArray(result)
|
||||
.filter(isDefined)
|
||||
.map(location => ({ ...location, uri: location.uri.toString() }))
|
||||
)
|
||||
)
|
||||
.toPromise()
|
||||
),
|
||||
{ defaultValue: [] }
|
||||
)
|
||||
}
|
||||
|
||||
public hasReferenceProvidersForDocument(textParameters: TextDocumentPositionParameters): Promise<boolean> {
|
||||
@ -128,26 +127,26 @@ class DefaultCodeIntelAPI implements CodeIntelAPI {
|
||||
request.providers.implementations.provideLocations(request.document, request.position)
|
||||
)
|
||||
}
|
||||
public getHover(textParameters: TextDocumentPositionParameters): Promise<HoverMerged | null | undefined> {
|
||||
public getHover(textParameters: TextDocumentPositionParameters): Promise<HoverMerged | null> {
|
||||
const request = requestFor(textParameters)
|
||||
return (
|
||||
return lastValueFrom(
|
||||
request.providers.hover
|
||||
.provideHover(request.document, request.position)
|
||||
// We intentionally don't use `defaultIfEmpty()` here because
|
||||
// that makes the popover load with an empty docstring.
|
||||
.pipe(map(result => fromHoverMerged([result])))
|
||||
.toPromise()
|
||||
.pipe(map(result => fromHoverMerged([result]))),
|
||||
{ defaultValue: null }
|
||||
)
|
||||
}
|
||||
public getDocumentHighlights(textParameters: TextDocumentPositionParameters): Promise<DocumentHighlight[]> {
|
||||
const request = requestFor(textParameters)
|
||||
return request.providers.documentHighlights
|
||||
.provideDocumentHighlights(request.document, request.position)
|
||||
.pipe(
|
||||
defaultIfEmpty(),
|
||||
return lastValueFrom(
|
||||
request.providers.documentHighlights.provideDocumentHighlights(request.document, request.position).pipe(
|
||||
defaultIfEmpty(undefined),
|
||||
map(result => result || [])
|
||||
)
|
||||
.toPromise()
|
||||
),
|
||||
{ defaultValue: [] }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -242,13 +241,8 @@ export function injectNewCodeintel(
|
||||
}
|
||||
|
||||
export function newCodeIntelExtensionHostAPI(codeintel: CodeIntelAPI): CodeIntelExtensionHostAPI {
|
||||
function thenMaybeLoadingResult<T>(promise: Observable<T>): Observable<MaybeLoadingResult<T>> {
|
||||
return promise.pipe(
|
||||
map(result => {
|
||||
const maybeLoadingResult: MaybeLoadingResult<T> = { isLoading: false, result }
|
||||
return maybeLoadingResult
|
||||
})
|
||||
)
|
||||
function thenMaybeLoadingResult<T>(result: T): MaybeLoadingResult<T> {
|
||||
return { isLoading: false, result }
|
||||
}
|
||||
|
||||
return {
|
||||
@ -257,22 +251,22 @@ export function newCodeIntelExtensionHostAPI(codeintel: CodeIntelAPI): CodeIntel
|
||||
},
|
||||
getLocations(id, parameters) {
|
||||
if (!id.startsWith('implementations_')) {
|
||||
return proxySubscribable(thenMaybeLoadingResult(of([])))
|
||||
return proxySubscribable(of({ isLoading: false, result: [] }))
|
||||
}
|
||||
return proxySubscribable(thenMaybeLoadingResult(from(codeintel.getImplementations(parameters))))
|
||||
return proxySubscribable(from(codeintel.getImplementations(parameters).then(thenMaybeLoadingResult)))
|
||||
},
|
||||
getDefinition(parameters) {
|
||||
return proxySubscribable(thenMaybeLoadingResult(from(codeintel.getDefinition(parameters))))
|
||||
return proxySubscribable(from(codeintel.getDefinition(parameters).then(thenMaybeLoadingResult)))
|
||||
},
|
||||
getReferences(parameters, context, scipParameters) {
|
||||
return proxySubscribable(
|
||||
thenMaybeLoadingResult(from(codeintel.getReferences(parameters, context, scipParameters)))
|
||||
from(codeintel.getReferences(parameters, context, scipParameters).then(thenMaybeLoadingResult))
|
||||
)
|
||||
},
|
||||
getDocumentHighlights: (textParameters: TextDocumentPositionParameters) =>
|
||||
proxySubscribable(from(codeintel.getDocumentHighlights(textParameters))),
|
||||
getHover: (textParameters: TextDocumentPositionParameters) =>
|
||||
proxySubscribable(thenMaybeLoadingResult(from(codeintel.getHover(textParameters)))),
|
||||
proxySubscribable(from(codeintel.getHover(textParameters).then(thenMaybeLoadingResult))),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import type { Observable, Unsubscribable } from 'rxjs'
|
||||
import { lastValueFrom, type Observable, type Unsubscribable } from 'rxjs'
|
||||
|
||||
import type { GraphQLResult } from '@sourcegraph/http-client'
|
||||
|
||||
@ -361,10 +361,9 @@ export function requestGraphQL<T>(query: string, vars?: { [name: string]: unknow
|
||||
)
|
||||
)
|
||||
}
|
||||
return context
|
||||
|
||||
.requestGraphQL<T, any>({ request: query, variables: vars as any, mightContainPrivateInfo: true })
|
||||
.toPromise()
|
||||
return lastValueFrom(
|
||||
context.requestGraphQL<T, any>({ request: query, variables: vars as any, mightContainPrivateInfo: true })
|
||||
)
|
||||
}
|
||||
|
||||
export function getSetting<T>(key: string): T | undefined {
|
||||
|
||||
@ -396,7 +396,7 @@ describe('getDefinitionURL', () => {
|
||||
Partial<ViewStateSpec>
|
||||
) => ''
|
||||
)
|
||||
await of<MaybeLoadingResult<Location[]>>({
|
||||
await of({
|
||||
isLoading: false,
|
||||
result: [{ uri: 'git://r3?c3#f' }],
|
||||
})
|
||||
@ -424,7 +424,7 @@ describe('getDefinitionURL', () => {
|
||||
describe('when the result is inside the current root', () => {
|
||||
it('emits the definition URL the user input revision (not commit SHA) of the root', () =>
|
||||
expect(
|
||||
of<MaybeLoadingResult<Location[]>>({
|
||||
of({
|
||||
isLoading: false,
|
||||
result: [{ uri: 'git://r3?c3#f' }],
|
||||
})
|
||||
@ -445,7 +445,7 @@ describe('getDefinitionURL', () => {
|
||||
describe('when the result is not inside the current root (different repo and/or commit)', () => {
|
||||
it('emits the definition URL with range', () =>
|
||||
expect(
|
||||
of<MaybeLoadingResult<Location[]>>({
|
||||
of({
|
||||
isLoading: false,
|
||||
result: [FIXTURE_LOCATION_CLIENT],
|
||||
})
|
||||
@ -464,7 +464,7 @@ describe('getDefinitionURL', () => {
|
||||
|
||||
it('emits the definition URL without range', () =>
|
||||
expect(
|
||||
of<MaybeLoadingResult<Location[]>>({
|
||||
of({
|
||||
isLoading: false,
|
||||
result: [{ ...FIXTURE_LOCATION_CLIENT, range: undefined }],
|
||||
})
|
||||
@ -485,7 +485,7 @@ describe('getDefinitionURL', () => {
|
||||
|
||||
it('emits the definition panel URL if there is more than 1 location result', () =>
|
||||
expect(
|
||||
of<MaybeLoadingResult<Location[]>>({
|
||||
of({
|
||||
isLoading: false,
|
||||
result: [FIXTURE_LOCATION_CLIENT, { ...FIXTURE_LOCATION, uri: 'other' }],
|
||||
})
|
||||
|
||||
@ -1,7 +1,18 @@
|
||||
import type { Remote } from 'comlink'
|
||||
import * as H from 'history'
|
||||
import { isEqual, uniqWith } from 'lodash'
|
||||
import { combineLatest, merge, type Observable, of, Subscription, type Unsubscribable, concat, from, EMPTY } from 'rxjs'
|
||||
import {
|
||||
combineLatest,
|
||||
merge,
|
||||
type Observable,
|
||||
of,
|
||||
Subscription,
|
||||
type Unsubscribable,
|
||||
concat,
|
||||
from,
|
||||
EMPTY,
|
||||
lastValueFrom,
|
||||
} from 'rxjs'
|
||||
import {
|
||||
catchError,
|
||||
delay,
|
||||
@ -246,7 +257,7 @@ export const getDefinitionURL =
|
||||
Partial<MaybeLoadingResult<UIDefinitionURL | null>>
|
||||
> => {
|
||||
if (definitions.length === 0) {
|
||||
return of<MaybeLoadingResult<UIDefinitionURL | null>>({ isLoading, result: null })
|
||||
return of({ isLoading, result: null })
|
||||
}
|
||||
|
||||
// Get unique definitions.
|
||||
@ -258,7 +269,7 @@ export const getDefinitionURL =
|
||||
workspaceRoots || [],
|
||||
parseRepoURI(parameters.textDocument.uri)
|
||||
)
|
||||
return of<MaybeLoadingResult<UIDefinitionURL | null>>({
|
||||
return of({
|
||||
isLoading,
|
||||
result: {
|
||||
url: urlToFile(
|
||||
@ -409,8 +420,8 @@ export function registerHoverContributions({
|
||||
const parameters: TextDocumentPositionParameters & URLToFileContext =
|
||||
JSON.parse(parametersString)
|
||||
|
||||
const { result } = await wrapRemoteObservable(extensionHostAPI.getDefinition(parameters))
|
||||
.pipe(
|
||||
const { result } = await lastValueFrom(
|
||||
wrapRemoteObservable(extensionHostAPI.getDefinition(parameters)).pipe(
|
||||
getDefinitionURL(
|
||||
{ urlToFile, requestGraphQL },
|
||||
{
|
||||
@ -425,7 +436,7 @@ export function registerHoverContributions({
|
||||
),
|
||||
first(({ isLoading, result }) => !isLoading || result !== null)
|
||||
)
|
||||
.toPromise()
|
||||
)
|
||||
|
||||
if (!result) {
|
||||
throw new Error('No definition found.')
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { Endpoint } from 'comlink'
|
||||
import { isObject } from 'lodash'
|
||||
import type { Observable, Subscribable, Subscription } from 'rxjs'
|
||||
import type { Observable, Subscription } from 'rxjs'
|
||||
|
||||
import type { DiffPart } from '@sourcegraph/codeintellify'
|
||||
import { hasProperty } from '@sourcegraph/common'
|
||||
@ -73,7 +73,7 @@ export interface PlatformContext {
|
||||
*
|
||||
* @deprecated Use useSettings instead
|
||||
*/
|
||||
readonly settings: Subscribable<SettingsCascadeOrError<Settings>>
|
||||
readonly settings: Observable<SettingsCascadeOrError<Settings>>
|
||||
|
||||
/**
|
||||
* Update the settings for the subject, either by inserting/changing a specific value or by overwriting the
|
||||
|
||||
@ -10,7 +10,7 @@ import type { Test } from 'mocha'
|
||||
import { readFile, mkdir } from 'mz/fs'
|
||||
import pTimeout from 'p-timeout'
|
||||
import * as prettier from 'prettier'
|
||||
import { Subject, Subscription, throwError } from 'rxjs'
|
||||
import { Subject, Subscription, lastValueFrom, throwError } from 'rxjs'
|
||||
import { first, timeoutWith } from 'rxjs/operators'
|
||||
|
||||
import { STATIC_ASSETS_PATH } from '@sourcegraph/build-config'
|
||||
@ -288,15 +288,15 @@ export const createSharedIntegrationTestContext = async <
|
||||
triggerRequest: () => Promise<void> | void,
|
||||
operationName: O
|
||||
): Promise<Parameters<TGraphQlOperations[O]>[0]> => {
|
||||
const requestPromise = graphQlRequests
|
||||
.pipe(
|
||||
const requestPromise = lastValueFrom(
|
||||
graphQlRequests.pipe(
|
||||
first(
|
||||
(request: GraphQLRequestEvent<TGraphQlOperationNames>): request is GraphQLRequestEvent<O> =>
|
||||
request.operationName === operationName
|
||||
),
|
||||
timeoutWith(4000, throwError(new Error(`Timeout waiting for GraphQL request "${operationName}"`)))
|
||||
)
|
||||
.toPromise()
|
||||
)
|
||||
await triggerRequest()
|
||||
const { variables } = await requestPromise
|
||||
return variables
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { Remote } from 'comlink'
|
||||
import { throwError, of, Subscription, type Unsubscribable, type Subscribable } from 'rxjs'
|
||||
import { throwError, of, Subscription, type Unsubscribable, type Observable } from 'rxjs'
|
||||
import type * as sourcegraph from 'sourcegraph'
|
||||
import { expect } from 'vitest'
|
||||
|
||||
@ -107,8 +107,9 @@ export async function integrationTestContext(
|
||||
}
|
||||
}
|
||||
|
||||
export function collectSubscribableValues<T>(subscribable: Subscribable<T>): T[] {
|
||||
export function collectSubscribableValues<T>(observable: Observable<T>): T[] {
|
||||
const values: T[] = []
|
||||
subscribable.subscribe(value => values.push(value))
|
||||
// eslint-disable-next-line rxjs/no-ignored-subscription
|
||||
observable.subscribe(value => values.push(value))
|
||||
return values
|
||||
}
|
||||
|
||||
@ -180,7 +180,7 @@ export function createValidationPipeline(
|
||||
combineLatest([
|
||||
// Validate immediately if the user has provided an initial input value
|
||||
concat(
|
||||
initialValue !== undefined ? of<InputValidationEvent>({ value: initialValue, validate: true }) : EMPTY,
|
||||
initialValue !== undefined ? of({ value: initialValue, validate: true }) : EMPTY,
|
||||
inputValidationEvents
|
||||
),
|
||||
inputReferences,
|
||||
|
||||
@ -206,7 +206,7 @@ export function infinityQuery<TData = any, TVariables extends AnyVariables = Any
|
||||
'query',
|
||||
createRequest(args.query, { ...initialVariables, ...variables })
|
||||
)
|
||||
return concat<Partial<OperationResultState<TData, TVariables>>>(
|
||||
return concat(
|
||||
of({ fetching: true, stale: false, restoring: false }),
|
||||
from(args.client.executeRequestOperation(operation).toPromise()).pipe(
|
||||
map(({ data, stale, operation, error, extensions }) => ({
|
||||
|
||||
@ -73,33 +73,6 @@ export default defineConfig(({ mode }) => {
|
||||
find: /^(.*)\.gql$/,
|
||||
replacement: '$1.gql.ts',
|
||||
},
|
||||
// In rxjs v6 these are directories and cannot be imported from directly in the production build.
|
||||
// The following error occurs:
|
||||
// Error [ERR_UNSUPPORTED_DIR_IMPORT]: Directory import '[...]/node_modules/rxjs/operators' is not supported resolving ES modules
|
||||
{
|
||||
find: /^rxjs\/(operators|fetch)$/,
|
||||
replacement: 'rxjs/$1/index.js',
|
||||
customResolver(source, importer, options) {
|
||||
// This is an hacky way to make the dev build work. @sourcegraph/telemetry uses a newer
|
||||
// version of rxjs (v7) where `rjx/operators` and `rxjs/fetch` are properly mapped
|
||||
// to their respective files in package.json.
|
||||
// Applying the same replacement to this version results in an error.
|
||||
// I tried various ways to prevent having the alias be applied to `@sourcegraph/telemetry`
|
||||
// without success:
|
||||
// - Removing this alias causes the production build to fail do the issue mentioned at the
|
||||
// top of this alias.
|
||||
// - Adding something like `{ ssr: { external: '@sourcegraph/telemetry' } }` to the config
|
||||
// does not prevent the alias from being applied. Maybe I don't understand how `external`
|
||||
// is supposed to work.
|
||||
// - Using a custom plugin that implements a custom resolveId function is somehow not being
|
||||
// run in the production build for `rxjs` imports. Maybe it has something to do with the
|
||||
// interop between vite and sveltekit.
|
||||
if (importer?.includes('@sourcegraph/telemetry')) {
|
||||
source = source.replace('/index.js', '')
|
||||
}
|
||||
return this.resolve(source, importer, options)
|
||||
},
|
||||
},
|
||||
// Without aliasing lodash to lodash-es we get the following error:
|
||||
// SyntaxError: Named export 'castArray' not found. The requested module 'lodash' is a CommonJS module, which may not support all module.exports as named exports.
|
||||
{
|
||||
|
||||
@ -89,10 +89,6 @@ esbuild(
|
||||
"path-browserify",
|
||||
"monaco-yaml/lib/esm/monaco.contribution",
|
||||
"monaco-yaml/lib/esm/yaml.worker",
|
||||
"rxjs/_esm5/internal/OuterSubscriber",
|
||||
"rxjs/_esm5/internal/util/subscribeToResult",
|
||||
"rxjs/_esm5/internal/util/subscribeToArray",
|
||||
"rxjs/_esm5/internal/Observable",
|
||||
],
|
||||
format = "cjs",
|
||||
platform = "node",
|
||||
|
||||
@ -8,7 +8,6 @@ import {
|
||||
stylePlugin,
|
||||
packageResolutionPlugin,
|
||||
monacoPlugin,
|
||||
RXJS_RESOLUTIONS,
|
||||
buildTimerPlugin,
|
||||
workerPlugin,
|
||||
} from '@sourcegraph/build-config/src/esbuild/plugins'
|
||||
@ -51,7 +50,6 @@ export function esbuildBuildOptions(ENVIRONMENT_CONFIG: EnvironmentConfig): esbu
|
||||
workerPlugin,
|
||||
packageResolutionPlugin({
|
||||
path: require.resolve('path-browserify'),
|
||||
...RXJS_RESOLUTIONS,
|
||||
...(ENVIRONMENT_CONFIG.DEV_WEB_BUILDER_OMIT_SLOW_DEPS
|
||||
? {
|
||||
// Monaco
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { type FC, useLayoutEffect, useRef, useState } from 'react'
|
||||
|
||||
import { type Location, useLocation } from 'react-router-dom'
|
||||
import { BehaviorSubject } from 'rxjs'
|
||||
import { first } from 'rxjs/operators'
|
||||
import { BehaviorSubject, firstValueFrom } from 'rxjs'
|
||||
|
||||
import type { PlatformContext } from '@sourcegraph/shared/src/platform/context'
|
||||
import { isSearchContextSpecAvailable } from '@sourcegraph/shared/src/search'
|
||||
@ -45,12 +44,13 @@ export const SearchQueryStateObserver: FC<SearchQueryStateObserverProps> = props
|
||||
location: locationSubject,
|
||||
isSearchContextAvailable: (searchContext: string) =>
|
||||
searchContextsEnabled
|
||||
? isSearchContextSpecAvailable({
|
||||
spec: searchContext,
|
||||
platformContext,
|
||||
})
|
||||
.pipe(first())
|
||||
.toPromise()
|
||||
? firstValueFrom(
|
||||
isSearchContextSpecAvailable({
|
||||
spec: searchContext,
|
||||
platformContext,
|
||||
}),
|
||||
{ defaultValue: false }
|
||||
)
|
||||
: Promise.resolve(false),
|
||||
}).subscribe(parsedSearchURLAndContext => {
|
||||
if (parsedSearchURLAndContext.query) {
|
||||
|
||||
@ -2,7 +2,7 @@ import type { Dispatch, SetStateAction } from 'react'
|
||||
|
||||
import type { QueryTuple, MutationTuple, QueryResult } from '@apollo/client'
|
||||
import { parse } from 'jsonc-parser'
|
||||
import type { Observable } from 'rxjs'
|
||||
import { type Observable, lastValueFrom } from 'rxjs'
|
||||
import { map } from 'rxjs/operators'
|
||||
|
||||
import { createAggregateError } from '@sourcegraph/common'
|
||||
@ -111,28 +111,30 @@ export const useUpdateExternalService = (
|
||||
export function updateExternalService(
|
||||
variables: UpdateExternalServiceVariables
|
||||
): Promise<UpdateExternalServiceResult['updateExternalService']> {
|
||||
return requestGraphQL<UpdateExternalServiceResult, UpdateExternalServiceVariables>(
|
||||
UPDATE_EXTERNAL_SERVICE,
|
||||
variables
|
||||
)
|
||||
.pipe(
|
||||
return lastValueFrom(
|
||||
requestGraphQL<UpdateExternalServiceResult, UpdateExternalServiceVariables>(
|
||||
UPDATE_EXTERNAL_SERVICE,
|
||||
variables
|
||||
).pipe(
|
||||
map(dataOrThrowErrors),
|
||||
map(data => data.updateExternalService)
|
||||
)
|
||||
.toPromise()
|
||||
)
|
||||
}
|
||||
|
||||
export async function deleteExternalService(externalService: Scalars['ID']): Promise<void> {
|
||||
const result = await requestGraphQL<DeleteExternalServiceResult, DeleteExternalServiceVariables>(
|
||||
gql`
|
||||
mutation DeleteExternalService($externalService: ID!) {
|
||||
deleteExternalService(externalService: $externalService) {
|
||||
alwaysNil
|
||||
const result = await lastValueFrom(
|
||||
requestGraphQL<DeleteExternalServiceResult, DeleteExternalServiceVariables>(
|
||||
gql`
|
||||
mutation DeleteExternalService($externalService: ID!) {
|
||||
deleteExternalService(externalService: $externalService) {
|
||||
alwaysNil
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ externalService }
|
||||
).toPromise()
|
||||
`,
|
||||
{ externalService }
|
||||
)
|
||||
)
|
||||
dataOrThrowErrors(result)
|
||||
}
|
||||
|
||||
|
||||
@ -1,18 +1,22 @@
|
||||
import { lastValueFrom } from 'rxjs'
|
||||
|
||||
import { gql, dataOrThrowErrors } from '@sourcegraph/http-client'
|
||||
|
||||
import { requestGraphQL } from '../../../backend/graphql'
|
||||
import type { CloseBatchChangeResult, CloseBatchChangeVariables } from '../../../graphql-operations'
|
||||
|
||||
export async function closeBatchChange({ batchChange, closeChangesets }: CloseBatchChangeVariables): Promise<void> {
|
||||
const result = await requestGraphQL<CloseBatchChangeResult, CloseBatchChangeVariables>(
|
||||
gql`
|
||||
mutation CloseBatchChange($batchChange: ID!, $closeChangesets: Boolean) {
|
||||
closeBatchChange(batchChange: $batchChange, closeChangesets: $closeChangesets) {
|
||||
id
|
||||
const result = await lastValueFrom(
|
||||
requestGraphQL<CloseBatchChangeResult, CloseBatchChangeVariables>(
|
||||
gql`
|
||||
mutation CloseBatchChange($batchChange: ID!, $closeChangesets: Boolean) {
|
||||
closeBatchChange(batchChange: $batchChange, closeChangesets: $closeChangesets) {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ batchChange, closeChangesets }
|
||||
).toPromise()
|
||||
`,
|
||||
{ batchChange, closeChangesets }
|
||||
)
|
||||
)
|
||||
dataOrThrowErrors(result)
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { QueryResult, QueryTuple } from '@apollo/client'
|
||||
import { EMPTY, type Observable } from 'rxjs'
|
||||
import { EMPTY, lastValueFrom, type Observable } from 'rxjs'
|
||||
import { expand, map, reduce } from 'rxjs/operators'
|
||||
|
||||
import { dataOrThrowErrors, gql, useLazyQuery, useQuery } from '@sourcegraph/http-client'
|
||||
@ -459,37 +459,39 @@ export const queryChangesets = ({
|
||||
)
|
||||
|
||||
export async function syncChangeset(changeset: Scalars['ID']): Promise<void> {
|
||||
const result = await requestGraphQL<SyncChangesetResult, SyncChangesetVariables>(
|
||||
gql`
|
||||
mutation SyncChangeset($changeset: ID!) {
|
||||
syncChangeset(changeset: $changeset) {
|
||||
alwaysNil
|
||||
const result = await lastValueFrom(
|
||||
requestGraphQL<SyncChangesetResult, SyncChangesetVariables>(
|
||||
gql`
|
||||
mutation SyncChangeset($changeset: ID!) {
|
||||
syncChangeset(changeset: $changeset) {
|
||||
alwaysNil
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ changeset }
|
||||
).toPromise()
|
||||
`,
|
||||
{ changeset }
|
||||
)
|
||||
)
|
||||
dataOrThrowErrors(result)
|
||||
}
|
||||
|
||||
export async function reenqueueChangeset(changeset: Scalars['ID']): Promise<ChangesetFields> {
|
||||
return requestGraphQL<ReenqueueChangesetResult, ReenqueueChangesetVariables>(
|
||||
gql`
|
||||
mutation ReenqueueChangeset($changeset: ID!) {
|
||||
reenqueueChangeset(changeset: $changeset) {
|
||||
...ChangesetFields
|
||||
return lastValueFrom(
|
||||
requestGraphQL<ReenqueueChangesetResult, ReenqueueChangesetVariables>(
|
||||
gql`
|
||||
mutation ReenqueueChangeset($changeset: ID!) {
|
||||
reenqueueChangeset(changeset: $changeset) {
|
||||
...ChangesetFields
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${changesetFieldsFragment}
|
||||
`,
|
||||
{ changeset }
|
||||
)
|
||||
.pipe(
|
||||
${changesetFieldsFragment}
|
||||
`,
|
||||
{ changeset }
|
||||
).pipe(
|
||||
map(dataOrThrowErrors),
|
||||
map(data => data.reenqueueChangeset)
|
||||
)
|
||||
.toPromise()
|
||||
)
|
||||
}
|
||||
|
||||
// Because thats the name in the API:
|
||||
@ -628,16 +630,18 @@ export const useChangesetCountsOverTime = (
|
||||
})
|
||||
|
||||
export async function deleteBatchChange(batchChange: Scalars['ID']): Promise<void> {
|
||||
const result = await requestGraphQL<DeleteBatchChangeResult, DeleteBatchChangeVariables>(
|
||||
gql`
|
||||
mutation DeleteBatchChange($batchChange: ID!) {
|
||||
deleteBatchChange(batchChange: $batchChange) {
|
||||
alwaysNil
|
||||
const result = await lastValueFrom(
|
||||
requestGraphQL<DeleteBatchChangeResult, DeleteBatchChangeVariables>(
|
||||
gql`
|
||||
mutation DeleteBatchChange($batchChange: ID!) {
|
||||
deleteBatchChange(batchChange: $batchChange) {
|
||||
alwaysNil
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ batchChange }
|
||||
).toPromise()
|
||||
`,
|
||||
{ batchChange }
|
||||
)
|
||||
)
|
||||
dataOrThrowErrors(result)
|
||||
}
|
||||
|
||||
@ -657,20 +661,20 @@ const changesetDiffFragment = gql`
|
||||
`
|
||||
|
||||
export async function getChangesetDiff(changeset: Scalars['ID']): Promise<string> {
|
||||
return requestGraphQL<ChangesetDiffResult, ChangesetDiffVariables>(
|
||||
gql`
|
||||
query ChangesetDiff($changeset: ID!) {
|
||||
node(id: $changeset) {
|
||||
__typename
|
||||
...ChangesetDiffFields
|
||||
return lastValueFrom(
|
||||
requestGraphQL<ChangesetDiffResult, ChangesetDiffVariables>(
|
||||
gql`
|
||||
query ChangesetDiff($changeset: ID!) {
|
||||
node(id: $changeset) {
|
||||
__typename
|
||||
...ChangesetDiffFields
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${changesetDiffFragment}
|
||||
`,
|
||||
{ changeset }
|
||||
)
|
||||
.pipe(
|
||||
${changesetDiffFragment}
|
||||
`,
|
||||
{ changeset }
|
||||
).pipe(
|
||||
map(dataOrThrowErrors),
|
||||
map(({ node }) => {
|
||||
if (!node) {
|
||||
@ -693,7 +697,7 @@ export async function getChangesetDiff(changeset: Scalars['ID']): Promise<string
|
||||
return commits[0].diff
|
||||
})
|
||||
)
|
||||
.toPromise()
|
||||
)
|
||||
}
|
||||
|
||||
const changesetScheduleEstimateFragment = gql`
|
||||
@ -703,20 +707,20 @@ const changesetScheduleEstimateFragment = gql`
|
||||
`
|
||||
|
||||
export async function getChangesetScheduleEstimate(changeset: Scalars['ID']): Promise<Scalars['DateTime'] | null> {
|
||||
return requestGraphQL<ChangesetScheduleEstimateResult, ChangesetScheduleEstimateVariables>(
|
||||
gql`
|
||||
query ChangesetScheduleEstimate($changeset: ID!) {
|
||||
node(id: $changeset) {
|
||||
__typename
|
||||
...ChangesetScheduleEstimateFields
|
||||
return lastValueFrom(
|
||||
requestGraphQL<ChangesetScheduleEstimateResult, ChangesetScheduleEstimateVariables>(
|
||||
gql`
|
||||
query ChangesetScheduleEstimate($changeset: ID!) {
|
||||
node(id: $changeset) {
|
||||
__typename
|
||||
...ChangesetScheduleEstimateFields
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${changesetScheduleEstimateFragment}
|
||||
`,
|
||||
{ changeset }
|
||||
)
|
||||
.pipe(
|
||||
${changesetScheduleEstimateFragment}
|
||||
`,
|
||||
{ changeset }
|
||||
).pipe(
|
||||
map(dataOrThrowErrors),
|
||||
map(({ node }) => {
|
||||
if (!node) {
|
||||
@ -730,20 +734,22 @@ export async function getChangesetScheduleEstimate(changeset: Scalars['ID']): Pr
|
||||
return node.scheduleEstimateAt
|
||||
})
|
||||
)
|
||||
.toPromise()
|
||||
)
|
||||
}
|
||||
|
||||
export async function detachChangesets(batchChange: Scalars['ID'], changesets: Scalars['ID'][]): Promise<void> {
|
||||
const result = await requestGraphQL<DetachChangesetsResult, DetachChangesetsVariables>(
|
||||
gql`
|
||||
mutation DetachChangesets($batchChange: ID!, $changesets: [ID!]!) {
|
||||
detachChangesets(batchChange: $batchChange, changesets: $changesets) {
|
||||
id
|
||||
const result = await lastValueFrom(
|
||||
requestGraphQL<DetachChangesetsResult, DetachChangesetsVariables>(
|
||||
gql`
|
||||
mutation DetachChangesets($batchChange: ID!, $changesets: [ID!]!) {
|
||||
detachChangesets(batchChange: $batchChange, changesets: $changesets) {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ batchChange, changesets }
|
||||
).toPromise()
|
||||
`,
|
||||
{ batchChange, changesets }
|
||||
)
|
||||
)
|
||||
dataOrThrowErrors(result)
|
||||
}
|
||||
|
||||
@ -752,30 +758,34 @@ export async function createChangesetComments(
|
||||
changesets: Scalars['ID'][],
|
||||
body: string
|
||||
): Promise<void> {
|
||||
const result = await requestGraphQL<CreateChangesetCommentsResult, CreateChangesetCommentsVariables>(
|
||||
gql`
|
||||
mutation CreateChangesetComments($batchChange: ID!, $changesets: [ID!]!, $body: String!) {
|
||||
createChangesetComments(batchChange: $batchChange, changesets: $changesets, body: $body) {
|
||||
id
|
||||
const result = await lastValueFrom(
|
||||
requestGraphQL<CreateChangesetCommentsResult, CreateChangesetCommentsVariables>(
|
||||
gql`
|
||||
mutation CreateChangesetComments($batchChange: ID!, $changesets: [ID!]!, $body: String!) {
|
||||
createChangesetComments(batchChange: $batchChange, changesets: $changesets, body: $body) {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ batchChange, changesets, body }
|
||||
).toPromise()
|
||||
`,
|
||||
{ batchChange, changesets, body }
|
||||
)
|
||||
)
|
||||
dataOrThrowErrors(result)
|
||||
}
|
||||
|
||||
export async function reenqueueChangesets(batchChange: Scalars['ID'], changesets: Scalars['ID'][]): Promise<void> {
|
||||
const result = await requestGraphQL<ReenqueueChangesetsResult, ReenqueueChangesetsVariables>(
|
||||
gql`
|
||||
mutation ReenqueueChangesets($batchChange: ID!, $changesets: [ID!]!) {
|
||||
reenqueueChangesets(batchChange: $batchChange, changesets: $changesets) {
|
||||
id
|
||||
const result = await lastValueFrom(
|
||||
requestGraphQL<ReenqueueChangesetsResult, ReenqueueChangesetsVariables>(
|
||||
gql`
|
||||
mutation ReenqueueChangesets($batchChange: ID!, $changesets: [ID!]!) {
|
||||
reenqueueChangesets(batchChange: $batchChange, changesets: $changesets) {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ batchChange, changesets }
|
||||
).toPromise()
|
||||
`,
|
||||
{ batchChange, changesets }
|
||||
)
|
||||
)
|
||||
dataOrThrowErrors(result)
|
||||
}
|
||||
|
||||
@ -784,30 +794,34 @@ export async function mergeChangesets(
|
||||
changesets: Scalars['ID'][],
|
||||
squash: boolean
|
||||
): Promise<void> {
|
||||
const result = await requestGraphQL<MergeChangesetsResult, MergeChangesetsVariables>(
|
||||
gql`
|
||||
mutation MergeChangesets($batchChange: ID!, $changesets: [ID!]!, $squash: Boolean!) {
|
||||
mergeChangesets(batchChange: $batchChange, changesets: $changesets, squash: $squash) {
|
||||
id
|
||||
const result = await lastValueFrom(
|
||||
requestGraphQL<MergeChangesetsResult, MergeChangesetsVariables>(
|
||||
gql`
|
||||
mutation MergeChangesets($batchChange: ID!, $changesets: [ID!]!, $squash: Boolean!) {
|
||||
mergeChangesets(batchChange: $batchChange, changesets: $changesets, squash: $squash) {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ batchChange, changesets, squash }
|
||||
).toPromise()
|
||||
`,
|
||||
{ batchChange, changesets, squash }
|
||||
)
|
||||
)
|
||||
dataOrThrowErrors(result)
|
||||
}
|
||||
|
||||
export async function closeChangesets(batchChange: Scalars['ID'], changesets: Scalars['ID'][]): Promise<void> {
|
||||
const result = await requestGraphQL<CloseChangesetsResult, CloseChangesetsVariables>(
|
||||
gql`
|
||||
mutation CloseChangesets($batchChange: ID!, $changesets: [ID!]!) {
|
||||
closeChangesets(batchChange: $batchChange, changesets: $changesets) {
|
||||
id
|
||||
const result = await lastValueFrom(
|
||||
requestGraphQL<CloseChangesetsResult, CloseChangesetsVariables>(
|
||||
gql`
|
||||
mutation CloseChangesets($batchChange: ID!, $changesets: [ID!]!) {
|
||||
closeChangesets(batchChange: $batchChange, changesets: $changesets) {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ batchChange, changesets }
|
||||
).toPromise()
|
||||
`,
|
||||
{ batchChange, changesets }
|
||||
)
|
||||
)
|
||||
dataOrThrowErrors(result)
|
||||
}
|
||||
|
||||
@ -816,16 +830,18 @@ export async function publishChangesets(
|
||||
changesets: Scalars['ID'][],
|
||||
draft: boolean
|
||||
): Promise<void> {
|
||||
const result = await requestGraphQL<PublishChangesetsResult, PublishChangesetsVariables>(
|
||||
gql`
|
||||
mutation PublishChangesets($batchChange: ID!, $changesets: [ID!]!, $draft: Boolean!) {
|
||||
publishChangesets(batchChange: $batchChange, changesets: $changesets, draft: $draft) {
|
||||
id
|
||||
const result = await lastValueFrom(
|
||||
requestGraphQL<PublishChangesetsResult, PublishChangesetsVariables>(
|
||||
gql`
|
||||
mutation PublishChangesets($batchChange: ID!, $changesets: [ID!]!, $draft: Boolean!) {
|
||||
publishChangesets(batchChange: $batchChange, changesets: $changesets, draft: $draft) {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ batchChange, changesets, draft }
|
||||
).toPromise()
|
||||
`,
|
||||
{ batchChange, changesets, draft }
|
||||
)
|
||||
)
|
||||
dataOrThrowErrors(result)
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { Observable } from 'rxjs'
|
||||
import { lastValueFrom, type Observable } from 'rxjs'
|
||||
import { map } from 'rxjs/operators'
|
||||
|
||||
import { gql, dataOrThrowErrors } from '@sourcegraph/http-client'
|
||||
@ -139,49 +139,49 @@ export const createBatchChange = ({
|
||||
batchSpec,
|
||||
publicationStates,
|
||||
}: CreateBatchChangeVariables): Promise<CreateBatchChangeResult['createBatchChange']> =>
|
||||
requestGraphQL<CreateBatchChangeResult, CreateBatchChangeVariables>(
|
||||
gql`
|
||||
mutation CreateBatchChange($batchSpec: ID!, $publicationStates: [ChangesetSpecPublicationStateInput!]) {
|
||||
createBatchChange(batchSpec: $batchSpec, publicationStates: $publicationStates) {
|
||||
id
|
||||
url
|
||||
lastValueFrom(
|
||||
requestGraphQL<CreateBatchChangeResult, CreateBatchChangeVariables>(
|
||||
gql`
|
||||
mutation CreateBatchChange($batchSpec: ID!, $publicationStates: [ChangesetSpecPublicationStateInput!]) {
|
||||
createBatchChange(batchSpec: $batchSpec, publicationStates: $publicationStates) {
|
||||
id
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ batchSpec, publicationStates }
|
||||
)
|
||||
.pipe(
|
||||
`,
|
||||
{ batchSpec, publicationStates }
|
||||
).pipe(
|
||||
map(dataOrThrowErrors),
|
||||
map(data => data.createBatchChange)
|
||||
)
|
||||
.toPromise()
|
||||
)
|
||||
|
||||
export const applyBatchChange = ({
|
||||
batchSpec,
|
||||
batchChange,
|
||||
publicationStates,
|
||||
}: ApplyBatchChangeVariables): Promise<ApplyBatchChangeResult['applyBatchChange']> =>
|
||||
requestGraphQL<ApplyBatchChangeResult, ApplyBatchChangeVariables>(
|
||||
gql`
|
||||
mutation ApplyBatchChange(
|
||||
$batchSpec: ID!
|
||||
$batchChange: ID!
|
||||
$publicationStates: [ChangesetSpecPublicationStateInput!]
|
||||
) {
|
||||
applyBatchChange(
|
||||
batchSpec: $batchSpec
|
||||
ensureBatchChange: $batchChange
|
||||
publicationStates: $publicationStates
|
||||
lastValueFrom(
|
||||
requestGraphQL<ApplyBatchChangeResult, ApplyBatchChangeVariables>(
|
||||
gql`
|
||||
mutation ApplyBatchChange(
|
||||
$batchSpec: ID!
|
||||
$batchChange: ID!
|
||||
$publicationStates: [ChangesetSpecPublicationStateInput!]
|
||||
) {
|
||||
id
|
||||
url
|
||||
applyBatchChange(
|
||||
batchSpec: $batchSpec
|
||||
ensureBatchChange: $batchChange
|
||||
publicationStates: $publicationStates
|
||||
) {
|
||||
id
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ batchSpec, batchChange, publicationStates }
|
||||
)
|
||||
.pipe(
|
||||
`,
|
||||
{ batchSpec, batchChange, publicationStates }
|
||||
).pipe(
|
||||
map(dataOrThrowErrors),
|
||||
map(data => data.applyBatchChange)
|
||||
)
|
||||
.toPromise()
|
||||
)
|
||||
|
||||
@ -41,8 +41,8 @@ export const EnqueueForm: FunctionComponent<EnqueueFormProps> = ({
|
||||
|
||||
const queueResultLength = indexes?.queueAutoIndexJobsForRepo.length || 0
|
||||
setQueueResult(queueResultLength)
|
||||
if (queueResultLength > 0) {
|
||||
querySubject.next(indexes?.queueAutoIndexJobsForRepo[0].inputCommit)
|
||||
if (queueResultLength > 0 && indexes?.queueAutoIndexJobsForRepo[0].inputCommit !== undefined) {
|
||||
querySubject.next(indexes.queueAutoIndexJobsForRepo[0].inputCommit)
|
||||
}
|
||||
} catch (error) {
|
||||
setEnqueueError(error)
|
||||
|
||||
@ -170,7 +170,7 @@ export const CodeIntelPreciseIndexesPage: FunctionComponent<CodeIntelPreciseInde
|
||||
}, [indexerData?.indexerKeys])
|
||||
|
||||
// Poke filtered connection to refresh
|
||||
const refresh = useMemo(() => new Subject<undefined>(), [])
|
||||
const refresh = useMemo(() => new Subject<void>(), [])
|
||||
const querySubject = useMemo(() => new Subject<string>(), [])
|
||||
|
||||
// State used to control bulk index selection
|
||||
|
||||
@ -122,7 +122,7 @@ async function getLangStats(inputs: GetInsightContentInputs): Promise<Categorica
|
||||
)
|
||||
.toPromise()
|
||||
|
||||
if (stats.languages.length === 0) {
|
||||
if (!stats || stats.languages.length === 0) {
|
||||
throw new Error("We couldn't find the language statistics, try changing the repository.")
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ import React, { useContext, useMemo } from 'react'
|
||||
|
||||
import classNames from 'classnames'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { lastValueFrom } from 'rxjs'
|
||||
|
||||
import type { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
|
||||
import { PageHeader, Container, Button, LoadingSpinner, useObservable, Link, Tooltip } from '@sourcegraph/wildcard'
|
||||
@ -40,7 +41,7 @@ export const InsightsDashboardCreationPage: React.FunctionComponent<
|
||||
throw new Error('You have to specify a dashboard visibility')
|
||||
}
|
||||
|
||||
const createdDashboard = await createDashboard({ name, owners: [owner] }).toPromise()
|
||||
const createdDashboard = await lastValueFrom(createDashboard({ name, owners: [owner] }))
|
||||
|
||||
telemetryService.log('CodeInsightsDashboardCreationPageSubmitClick')
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import { type FC, useContext, useMemo } from 'react'
|
||||
import classNames from 'classnames'
|
||||
import MapSearchIcon from 'mdi-react/MapSearchIcon'
|
||||
import { useParams, useNavigate } from 'react-router-dom'
|
||||
import { lastValueFrom } from 'rxjs'
|
||||
|
||||
import {
|
||||
Button,
|
||||
@ -70,13 +71,15 @@ export const EditDashboardPage: FC = props => {
|
||||
throw new Error('You have to specify a dashboard visibility')
|
||||
}
|
||||
|
||||
const updatedDashboard = await updateDashboard({
|
||||
id: dashboard.id,
|
||||
nextDashboardInput: {
|
||||
name,
|
||||
owners: [owner],
|
||||
},
|
||||
}).toPromise()
|
||||
const updatedDashboard = await lastValueFrom(
|
||||
updateDashboard({
|
||||
id: dashboard.id,
|
||||
nextDashboardInput: {
|
||||
name,
|
||||
owners: [owner],
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
navigate(`/insights/dashboards/${updatedDashboard.id}`)
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { lastValueFrom } from 'rxjs'
|
||||
|
||||
import { dataOrThrowErrors, gql } from '@sourcegraph/http-client'
|
||||
import type { GraphQLResult } from '@sourcegraph/http-client'
|
||||
|
||||
import { requestGraphQL } from '../../backend/graphql'
|
||||
import type { InputMaybe, RepositoriesByNamesResult, RepositoriesByNamesVariables } from '../../graphql-operations'
|
||||
@ -27,14 +28,13 @@ export async function fetchRepositoriesByNames(
|
||||
let after: InputMaybe<string> = null
|
||||
|
||||
while (true) {
|
||||
const result: GraphQLResult<RepositoriesByNamesResult> = await requestGraphQL<
|
||||
RepositoriesByNamesResult,
|
||||
RepositoriesByNamesVariables
|
||||
>(query, {
|
||||
names,
|
||||
first,
|
||||
after,
|
||||
}).toPromise()
|
||||
const result = await lastValueFrom(
|
||||
requestGraphQL<RepositoriesByNamesResult, RepositoriesByNamesVariables>(query, {
|
||||
names,
|
||||
first,
|
||||
after,
|
||||
})
|
||||
)
|
||||
|
||||
const data: RepositoriesByNamesResult = dataOrThrowErrors(result)
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { concat, type Observable } from 'rxjs'
|
||||
import { concat, lastValueFrom, type Observable } from 'rxjs'
|
||||
import { map, mergeMap } from 'rxjs/operators'
|
||||
|
||||
import { createAggregateError } from '@sourcegraph/common'
|
||||
@ -65,19 +65,19 @@ export function createOrganization(args: {
|
||||
/** The new organization's display name (e.g. full name) in the organization profile. */
|
||||
displayName?: string
|
||||
}): Promise<CreateOrganizationResult['createOrganization']> {
|
||||
return requestGraphQL<CreateOrganizationResult, CreateOrganizationVariables>(
|
||||
gql`
|
||||
mutation CreateOrganization($name: String!, $displayName: String) {
|
||||
createOrganization(name: $name, displayName: $displayName) {
|
||||
id
|
||||
name
|
||||
settingsURL
|
||||
return lastValueFrom(
|
||||
requestGraphQL<CreateOrganizationResult, CreateOrganizationVariables>(
|
||||
gql`
|
||||
mutation CreateOrganization($name: String!, $displayName: String) {
|
||||
createOrganization(name: $name, displayName: $displayName) {
|
||||
id
|
||||
name
|
||||
settingsURL
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ name: args.name, displayName: args.displayName ?? null }
|
||||
)
|
||||
.pipe(
|
||||
`,
|
||||
{ name: args.name, displayName: args.displayName ?? null }
|
||||
).pipe(
|
||||
mergeMap(({ data, errors }) => {
|
||||
if (!data?.createOrganization) {
|
||||
eventLogger.log('NewOrgFailed')
|
||||
@ -87,7 +87,7 @@ export function createOrganization(args: {
|
||||
return concat(refreshAuthenticatedUser(), [data.createOrganization])
|
||||
})
|
||||
)
|
||||
.toPromise()
|
||||
)
|
||||
}
|
||||
|
||||
export const REMOVE_USER_FROM_ORGANIZATION_QUERY = gql`
|
||||
|
||||
@ -2,6 +2,7 @@ import React, { useCallback, useState } from 'react'
|
||||
|
||||
import { mdiPlus, mdiEmailOpenOutline, mdiClose } from '@mdi/js'
|
||||
import classNames from 'classnames'
|
||||
import { lastValueFrom } from 'rxjs'
|
||||
import { map } from 'rxjs/operators'
|
||||
|
||||
import { asError, createAggregateError, isErrorLike } from '@sourcegraph/common'
|
||||
@ -204,25 +205,25 @@ function inviteUserToOrganization(
|
||||
username: string,
|
||||
organization: Scalars['ID']
|
||||
): Promise<InviteUserToOrganizationResult['inviteUserToOrganization']> {
|
||||
return requestGraphQL<InviteUserToOrganizationResult, InviteUserToOrganizationVariables>(
|
||||
gql`
|
||||
mutation InviteUserToOrganization($organization: ID!, $username: String!) {
|
||||
inviteUserToOrganization(organization: $organization, username: $username) {
|
||||
...InviteUserToOrganizationFields
|
||||
return lastValueFrom(
|
||||
requestGraphQL<InviteUserToOrganizationResult, InviteUserToOrganizationVariables>(
|
||||
gql`
|
||||
mutation InviteUserToOrganization($organization: ID!, $username: String!) {
|
||||
inviteUserToOrganization(organization: $organization, username: $username) {
|
||||
...InviteUserToOrganizationFields
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment InviteUserToOrganizationFields on InviteUserToOrganizationResult {
|
||||
sentInvitationEmail
|
||||
invitationURL
|
||||
fragment InviteUserToOrganizationFields on InviteUserToOrganizationResult {
|
||||
sentInvitationEmail
|
||||
invitationURL
|
||||
}
|
||||
`,
|
||||
{
|
||||
username,
|
||||
organization,
|
||||
}
|
||||
`,
|
||||
{
|
||||
username,
|
||||
organization,
|
||||
}
|
||||
)
|
||||
.pipe(
|
||||
).pipe(
|
||||
map(({ data, errors }) => {
|
||||
if (!data?.inviteUserToOrganization || (errors && errors.length > 0)) {
|
||||
eventLogger.log('InviteOrgMemberFailed')
|
||||
@ -232,7 +233,7 @@ function inviteUserToOrganization(
|
||||
return data.inviteUserToOrganization
|
||||
})
|
||||
)
|
||||
.toPromise()
|
||||
)
|
||||
}
|
||||
|
||||
function addUserToOrganization(username: string, organization: Scalars['ID']): Promise<void> {
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
* Provides convenience functions for interacting with the Sourcegraph API from tests.
|
||||
*/
|
||||
|
||||
import { zip, timer, concat, throwError, defer, type Observable } from 'rxjs'
|
||||
import { zip, timer, concat, throwError, defer, type Observable, lastValueFrom } from 'rxjs'
|
||||
import { map, tap, retryWhen, delayWhen, take, mergeMap } from 'rxjs/operators'
|
||||
|
||||
import { isErrorLike, createAggregateError, logger } from '@sourcegraph/common'
|
||||
@ -261,40 +261,41 @@ export function getExternalServices(
|
||||
uniqueDisplayName?: string
|
||||
} = {}
|
||||
): Promise<ExternalServiceNodeFields[]> {
|
||||
return gqlClient
|
||||
.queryGraphQL<ExternalServicesRegressionResult, ExternalServicesRegressionVariables>(
|
||||
gql`
|
||||
query ExternalServicesRegression($first: Int) {
|
||||
externalServices(first: $first) {
|
||||
nodes {
|
||||
...ExternalServiceNodeFields
|
||||
return lastValueFrom(
|
||||
gqlClient
|
||||
.queryGraphQL<ExternalServicesRegressionResult, ExternalServicesRegressionVariables>(
|
||||
gql`
|
||||
query ExternalServicesRegression($first: Int) {
|
||||
externalServices(first: $first) {
|
||||
nodes {
|
||||
...ExternalServiceNodeFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment ExternalServiceNodeFields on ExternalService {
|
||||
id
|
||||
kind
|
||||
displayName
|
||||
config
|
||||
createdAt
|
||||
updatedAt
|
||||
warning
|
||||
}
|
||||
`,
|
||||
{ first: 100 }
|
||||
)
|
||||
.pipe(
|
||||
map(dataOrThrowErrors),
|
||||
map(({ externalServices }) =>
|
||||
externalServices.nodes.filter(
|
||||
({ displayName, kind }) =>
|
||||
(options.uniqueDisplayName === undefined || options.uniqueDisplayName === displayName) &&
|
||||
(options.kind === undefined || options.kind === kind)
|
||||
fragment ExternalServiceNodeFields on ExternalService {
|
||||
id
|
||||
kind
|
||||
displayName
|
||||
config
|
||||
createdAt
|
||||
updatedAt
|
||||
warning
|
||||
}
|
||||
`,
|
||||
{ first: 100 }
|
||||
)
|
||||
.pipe(
|
||||
map(dataOrThrowErrors),
|
||||
map(({ externalServices }) =>
|
||||
externalServices.nodes.filter(
|
||||
({ displayName, kind }) =>
|
||||
(options.uniqueDisplayName === undefined || options.uniqueDisplayName === displayName) &&
|
||||
(options.kind === undefined || options.kind === kind)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
.toPromise()
|
||||
)
|
||||
}
|
||||
|
||||
export async function updateExternalService(
|
||||
@ -448,22 +449,23 @@ export async function setTosAccepted(gqlClient: GraphQLClient, userID: Scalars['
|
||||
* dependency-injected `requestGraphQL`.
|
||||
*/
|
||||
export function currentProductVersion(gqlClient: GraphQLClient): Promise<string> {
|
||||
return gqlClient
|
||||
.queryGraphQL<SiteProductVersionResult, SiteProductVersionVariables>(
|
||||
gql`
|
||||
query SiteProductVersion {
|
||||
site {
|
||||
productVersion
|
||||
return lastValueFrom(
|
||||
gqlClient
|
||||
.queryGraphQL<SiteProductVersionResult, SiteProductVersionVariables>(
|
||||
gql`
|
||||
query SiteProductVersion {
|
||||
site {
|
||||
productVersion
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{}
|
||||
)
|
||||
.pipe(
|
||||
map(dataOrThrowErrors),
|
||||
map(({ site }) => site.productVersion)
|
||||
)
|
||||
.toPromise()
|
||||
`,
|
||||
{}
|
||||
)
|
||||
.pipe(
|
||||
map(dataOrThrowErrors),
|
||||
map(({ site }) => site.productVersion)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -473,16 +475,16 @@ export function currentProductVersion(gqlClient: GraphQLClient): Promise<string>
|
||||
export function getViewerSettings({
|
||||
requestGraphQL,
|
||||
}: Pick<PlatformContext, 'requestGraphQL'>): Promise<ViewerSettingsResult['viewerSettings']> {
|
||||
return requestGraphQL<ViewerSettingsResult, ViewerSettingsVariables>({
|
||||
request: viewerSettingsQuery,
|
||||
variables: {},
|
||||
mightContainPrivateInfo: true,
|
||||
})
|
||||
.pipe(
|
||||
return lastValueFrom(
|
||||
requestGraphQL<ViewerSettingsResult, ViewerSettingsVariables>({
|
||||
request: viewerSettingsQuery,
|
||||
variables: {},
|
||||
mightContainPrivateInfo: true,
|
||||
}).pipe(
|
||||
map(dataOrThrowErrors),
|
||||
map(data => data.viewerSettings)
|
||||
)
|
||||
.toPromise()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -631,55 +633,54 @@ export function createUser(
|
||||
* TODO(beyang): remove this after the corresponding API in the main code has been updated to use a
|
||||
* dependency-injected `requestGraphQL`.
|
||||
*/
|
||||
export async function getUser(
|
||||
export function getUser(
|
||||
{ requestGraphQL }: Pick<PlatformContext, 'requestGraphQL'>,
|
||||
username: string
|
||||
): Promise<UserResult['user']> {
|
||||
const user = await requestGraphQL<UserResult, UserVariables>({
|
||||
request: gql`
|
||||
query User($username: String!) {
|
||||
user(username: $username) {
|
||||
__typename
|
||||
id
|
||||
username
|
||||
displayName
|
||||
url
|
||||
settingsURL
|
||||
avatarURL
|
||||
viewerCanAdminister
|
||||
siteAdmin
|
||||
createdAt
|
||||
emails {
|
||||
email
|
||||
verified
|
||||
}
|
||||
organizations {
|
||||
nodes {
|
||||
id
|
||||
displayName
|
||||
name
|
||||
return lastValueFrom(
|
||||
requestGraphQL<UserResult, UserVariables>({
|
||||
request: gql`
|
||||
query User($username: String!) {
|
||||
user(username: $username) {
|
||||
__typename
|
||||
id
|
||||
username
|
||||
displayName
|
||||
url
|
||||
settingsURL
|
||||
avatarURL
|
||||
viewerCanAdminister
|
||||
siteAdmin
|
||||
createdAt
|
||||
emails {
|
||||
email
|
||||
verified
|
||||
}
|
||||
}
|
||||
settingsCascade {
|
||||
subjects {
|
||||
latestSettings {
|
||||
organizations {
|
||||
nodes {
|
||||
id
|
||||
contents
|
||||
displayName
|
||||
name
|
||||
}
|
||||
}
|
||||
settingsCascade {
|
||||
subjects {
|
||||
latestSettings {
|
||||
id
|
||||
contents
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { username },
|
||||
mightContainPrivateInfo: true,
|
||||
})
|
||||
.pipe(
|
||||
`,
|
||||
variables: { username },
|
||||
mightContainPrivateInfo: true,
|
||||
}).pipe(
|
||||
map(dataOrThrowErrors),
|
||||
map(({ user }) => user)
|
||||
)
|
||||
.toPromise()
|
||||
return user
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -748,86 +749,86 @@ export function search(
|
||||
version: SearchVersion,
|
||||
patternType: SearchPatternType
|
||||
): Promise<SearchResult['search']> {
|
||||
return requestGraphQL<SearchResult, SearchVariables>({
|
||||
request: gql`
|
||||
query Search($query: String!, $version: SearchVersion!, $patternType: SearchPatternType!) {
|
||||
search(query: $query, version: $version, patternType: $patternType) {
|
||||
results {
|
||||
__typename
|
||||
limitHit
|
||||
matchCount
|
||||
approximateResultCount
|
||||
missing {
|
||||
name
|
||||
}
|
||||
cloning {
|
||||
name
|
||||
}
|
||||
timedout {
|
||||
name
|
||||
}
|
||||
indexUnavailable
|
||||
dynamicFilters {
|
||||
value
|
||||
label
|
||||
count
|
||||
limitHit
|
||||
kind
|
||||
}
|
||||
return lastValueFrom(
|
||||
requestGraphQL<SearchResult, SearchVariables>({
|
||||
request: gql`
|
||||
query Search($query: String!, $version: SearchVersion!, $patternType: SearchPatternType!) {
|
||||
search(query: $query, version: $version, patternType: $patternType) {
|
||||
results {
|
||||
__typename
|
||||
... on Repository {
|
||||
id
|
||||
limitHit
|
||||
matchCount
|
||||
approximateResultCount
|
||||
missing {
|
||||
name
|
||||
...GenericSearchResultFields
|
||||
}
|
||||
... on FileMatch {
|
||||
file {
|
||||
path
|
||||
url
|
||||
commit {
|
||||
oid
|
||||
cloning {
|
||||
name
|
||||
}
|
||||
timedout {
|
||||
name
|
||||
}
|
||||
indexUnavailable
|
||||
dynamicFilters {
|
||||
value
|
||||
label
|
||||
count
|
||||
limitHit
|
||||
kind
|
||||
}
|
||||
results {
|
||||
__typename
|
||||
... on Repository {
|
||||
id
|
||||
name
|
||||
...GenericSearchResultFields
|
||||
}
|
||||
... on FileMatch {
|
||||
file {
|
||||
path
|
||||
url
|
||||
commit {
|
||||
oid
|
||||
}
|
||||
}
|
||||
repository {
|
||||
name
|
||||
url
|
||||
}
|
||||
limitHit
|
||||
symbols {
|
||||
name
|
||||
containerName
|
||||
url
|
||||
kind
|
||||
}
|
||||
lineMatches {
|
||||
preview
|
||||
lineNumber
|
||||
offsetAndLengths
|
||||
}
|
||||
}
|
||||
repository {
|
||||
name
|
||||
url
|
||||
}
|
||||
limitHit
|
||||
symbols {
|
||||
name
|
||||
containerName
|
||||
url
|
||||
kind
|
||||
}
|
||||
lineMatches {
|
||||
preview
|
||||
lineNumber
|
||||
offsetAndLengths
|
||||
... on CommitSearchResult {
|
||||
...GenericSearchResultFields
|
||||
}
|
||||
}
|
||||
... on CommitSearchResult {
|
||||
...GenericSearchResultFields
|
||||
}
|
||||
}
|
||||
alert {
|
||||
title
|
||||
description
|
||||
proposedQueries {
|
||||
alert {
|
||||
title
|
||||
description
|
||||
query
|
||||
proposedQueries {
|
||||
description
|
||||
query
|
||||
}
|
||||
}
|
||||
elapsedMilliseconds
|
||||
}
|
||||
elapsedMilliseconds
|
||||
}
|
||||
}
|
||||
}
|
||||
${GenericSearchResultInterfaceFragment}
|
||||
`,
|
||||
variables: { query, version, patternType },
|
||||
mightContainPrivateInfo: false,
|
||||
})
|
||||
.pipe(
|
||||
${GenericSearchResultInterfaceFragment}
|
||||
`,
|
||||
variables: { query, version, patternType },
|
||||
mightContainPrivateInfo: false,
|
||||
}).pipe(
|
||||
map(dataOrThrowErrors),
|
||||
map(data => {
|
||||
if (!data.search) {
|
||||
@ -836,7 +837,7 @@ export function search(
|
||||
return data.search
|
||||
})
|
||||
)
|
||||
.toPromise()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import * as jsonc from 'jsonc-parser'
|
||||
import { first } from 'lodash'
|
||||
import { throwError } from 'rxjs'
|
||||
import { lastValueFrom, throwError } from 'rxjs'
|
||||
import { catchError, map } from 'rxjs/operators'
|
||||
import { Key } from 'ts-key-enum'
|
||||
|
||||
@ -131,7 +131,7 @@ export async function createAuthProvider(
|
||||
gqlClient: GraphQLClient,
|
||||
authProvider: GitHubAuthProvider | GitLabAuthProvider | OpenIDConnectAuthProvider | SAMLAuthProvider
|
||||
): Promise<ResourceDestructor> {
|
||||
const siteConfig = await fetchSiteConfiguration(gqlClient).toPromise()
|
||||
const siteConfig = await lastValueFrom(fetchSiteConfiguration(gqlClient))
|
||||
const siteConfigParsed: SiteConfiguration = jsonc.parse(siteConfig.configuration.effectiveContents)
|
||||
const authProviders = siteConfigParsed['auth.providers']
|
||||
if (
|
||||
@ -185,7 +185,7 @@ export async function ensureNewOrganization(
|
||||
{ requestGraphQL }: Pick<PlatformContext, 'requestGraphQL'>,
|
||||
variables: CreateOrganizationVariables
|
||||
): Promise<{ destroy: ResourceDestructor; result: CreateOrganizationResult['createOrganization'] }> {
|
||||
const matchingOrgs = (await fetchAllOrganizations({ requestGraphQL }, { first: 1000 }).toPromise()).nodes.filter(
|
||||
const matchingOrgs = (await lastValueFrom(fetchAllOrganizations({ requestGraphQL }, { first: 1000 }))).nodes.filter(
|
||||
org => org.name === variables.name
|
||||
)
|
||||
if (matchingOrgs.length > 1) {
|
||||
@ -194,7 +194,7 @@ export async function ensureNewOrganization(
|
||||
if (matchingOrgs.length === 1) {
|
||||
await deleteOrganization({ requestGraphQL }, matchingOrgs[0].id).toPromise()
|
||||
}
|
||||
const createdOrg = await createOrganization({ requestGraphQL }, variables).toPromise()
|
||||
const createdOrg = await lastValueFrom(createOrganization({ requestGraphQL }, variables))
|
||||
return {
|
||||
destroy: () => deleteOrganization({ requestGraphQL }, createdOrg.id).toPromise(),
|
||||
result: createdOrg,
|
||||
@ -239,15 +239,15 @@ export async function editSiteConfig(
|
||||
gqlClient: GraphQLClient,
|
||||
...edits: ((contents: string) => jsonc.Edit[])[]
|
||||
): Promise<{ destroy: ResourceDestructor; result: boolean }> {
|
||||
const origConfig = await fetchSiteConfiguration(gqlClient).toPromise()
|
||||
const origConfig = await lastValueFrom(fetchSiteConfiguration(gqlClient))
|
||||
let newContents = origConfig.configuration.effectiveContents
|
||||
for (const editFunc of edits) {
|
||||
newContents = jsonc.applyEdits(newContents, editFunc(newContents))
|
||||
}
|
||||
return {
|
||||
result: await updateSiteConfiguration(gqlClient, origConfig.configuration.id, newContents).toPromise(),
|
||||
result: await lastValueFrom(updateSiteConfiguration(gqlClient, origConfig.configuration.id, newContents)),
|
||||
destroy: async () => {
|
||||
const site = await fetchSiteConfiguration(gqlClient).toPromise()
|
||||
const site = await lastValueFrom(fetchSiteConfiguration(gqlClient))
|
||||
await updateSiteConfiguration(
|
||||
gqlClient,
|
||||
site.configuration.id,
|
||||
|
||||
@ -175,7 +175,7 @@ export const RepoContainer: FC<RepoContainerProps> = props => {
|
||||
}
|
||||
|
||||
if (isCloneInProgressErrorLike(error)) {
|
||||
return of<ErrorLike>(asError(error))
|
||||
return of(asError(error))
|
||||
}
|
||||
|
||||
throw error
|
||||
@ -185,7 +185,7 @@ export const RepoContainer: FC<RepoContainerProps> = props => {
|
||||
)
|
||||
.pipe(
|
||||
repeatUntil(value => !isCloneInProgressErrorLike(value), { delay: 1000 }),
|
||||
catchError(error => of<ErrorLike>(asError(error)))
|
||||
catchError(error => of(asError(error)))
|
||||
),
|
||||
[repoName, revision]
|
||||
)
|
||||
|
||||
@ -3,7 +3,7 @@ import { useMemo } from 'react'
|
||||
import { EventStreamContentType, fetchEventSource } from '@microsoft/fetch-event-source'
|
||||
import { formatDistanceStrict } from 'date-fns'
|
||||
import { truncate } from 'lodash'
|
||||
import { Observable, of } from 'rxjs'
|
||||
import { Observable, lastValueFrom, of } from 'rxjs'
|
||||
import { catchError, map, throttleTime } from 'rxjs/operators'
|
||||
|
||||
import { type ErrorLike, memoizeObservable } from '@sourcegraph/common'
|
||||
@ -192,25 +192,25 @@ const fetchBlameViaStreaming = memoizeObservable(
|
||||
)
|
||||
|
||||
async function fetchRepositoryData(repoName: string): Promise<Omit<BlameHunkData, 'current'>> {
|
||||
return requestGraphQL<FirstCommitDateResult, FirstCommitDateVariables>(
|
||||
gql`
|
||||
query FirstCommitDate($repo: String!) {
|
||||
repository(name: $repo) {
|
||||
firstEverCommit {
|
||||
author {
|
||||
date
|
||||
return lastValueFrom(
|
||||
requestGraphQL<FirstCommitDateResult, FirstCommitDateVariables>(
|
||||
gql`
|
||||
query FirstCommitDate($repo: String!) {
|
||||
repository(name: $repo) {
|
||||
firstEverCommit {
|
||||
author {
|
||||
date
|
||||
}
|
||||
}
|
||||
externalURLs {
|
||||
url
|
||||
serviceKind
|
||||
}
|
||||
}
|
||||
externalURLs {
|
||||
url
|
||||
serviceKind
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ repo: repoName }
|
||||
)
|
||||
.pipe(
|
||||
`,
|
||||
{ repo: repoName }
|
||||
).pipe(
|
||||
map(dataOrThrowErrors),
|
||||
map(({ repository }) => {
|
||||
const firstCommitDate = repository?.firstEverCommit?.author?.date
|
||||
@ -220,7 +220,7 @@ async function fetchRepositoryData(repoName: string): Promise<Omit<BlameHunkData
|
||||
}
|
||||
})
|
||||
)
|
||||
.toPromise()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -33,7 +33,7 @@ export class ToggleHistoryPanel extends React.PureComponent<
|
||||
navigate: NavigateFunction
|
||||
} & RepoHeaderContext
|
||||
> {
|
||||
private toggles = new Subject<boolean>()
|
||||
private toggles = new Subject<void>()
|
||||
private subscriptions = new Subscription()
|
||||
|
||||
/**
|
||||
|
||||
@ -7,7 +7,7 @@ import { Routes, Route } from 'react-router-dom'
|
||||
import { of } from 'rxjs'
|
||||
import { catchError } from 'rxjs/operators'
|
||||
|
||||
import { asError, type ErrorLike, isErrorLike } from '@sourcegraph/common'
|
||||
import { asError, isErrorLike } from '@sourcegraph/common'
|
||||
import type { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
|
||||
import type { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
|
||||
import { useObservable, ErrorMessage } from '@sourcegraph/wildcard'
|
||||
@ -46,10 +46,7 @@ export const RepoSettingsArea: React.FunctionComponent<React.PropsWithChildren<P
|
||||
}) => {
|
||||
const repoName = props.repoName
|
||||
const repoOrError = useObservable(
|
||||
useMemo(
|
||||
() => fetchSettingsAreaRepository(repoName).pipe(catchError(error => of<ErrorLike>(asError(error)))),
|
||||
[repoName]
|
||||
)
|
||||
useMemo(() => fetchSettingsAreaRepository(repoName).pipe(catchError(error => of(asError(error)))), [repoName])
|
||||
)
|
||||
|
||||
useBreadcrumb(useMemo(() => ({ key: 'settings', element: 'Settings' }), []))
|
||||
|
||||
@ -4,7 +4,7 @@ import type { FC } from 'react'
|
||||
import { type ApolloClient, useApolloClient } from '@apollo/client'
|
||||
import classNames from 'classnames'
|
||||
import * as jsonc from 'jsonc-parser'
|
||||
import { Subject, Subscription } from 'rxjs'
|
||||
import { lastValueFrom, Subject, Subscription } from 'rxjs'
|
||||
import { delay, mergeMap, retryWhen, tap, timeout } from 'rxjs/operators'
|
||||
|
||||
import { logger } from '@sourcegraph/common'
|
||||
@ -468,7 +468,7 @@ class SiteAdminConfigurationContent extends React.Component<Props, State> {
|
||||
|
||||
let restartToApply = false
|
||||
try {
|
||||
restartToApply = await updateSiteConfiguration(lastConfigurationID, newContents).toPromise<boolean>()
|
||||
restartToApply = await lastValueFrom(updateSiteConfiguration(lastConfigurationID, newContents))
|
||||
} catch (error) {
|
||||
logger.error(error)
|
||||
this.setState({
|
||||
@ -512,7 +512,7 @@ class SiteAdminConfigurationContent extends React.Component<Props, State> {
|
||||
this.setState({ restartToApply })
|
||||
|
||||
try {
|
||||
const site = await fetchSite().toPromise()
|
||||
const site = await lastValueFrom(fetchSite())
|
||||
|
||||
this.setState({
|
||||
site,
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import React, { useState, useCallback } from 'react'
|
||||
|
||||
import { lastValueFrom } from 'rxjs'
|
||||
|
||||
import { logger } from '@sourcegraph/common'
|
||||
import { useMutation } from '@sourcegraph/http-client'
|
||||
import { Text } from '@sourcegraph/wildcard'
|
||||
@ -202,8 +204,7 @@ export function useUserListActions(onEnd: (error?: any) => void): UseUserListAct
|
||||
const handleResetUserPassword = useCallback(
|
||||
([user]: SiteUser[]) => {
|
||||
if (confirm('Are you sure you want to reset the selected user password?')) {
|
||||
randomizeUserPassword(user.id)
|
||||
.toPromise()
|
||||
lastValueFrom(randomizeUserPassword(user.id))
|
||||
.then(({ resetPasswordURL, emailSent }) => {
|
||||
if (resetPasswordURL === null || emailSent) {
|
||||
createOnSuccess(
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import isAbsoluteURL from 'is-absolute-url'
|
||||
import { memoize, noop } from 'lodash'
|
||||
import { type Subscriber, type Subscription, fromEvent, of } from 'rxjs'
|
||||
import { type Subscriber, type Subscription, fromEvent, of, lastValueFrom } from 'rxjs'
|
||||
import { map } from 'rxjs/operators'
|
||||
|
||||
import {
|
||||
@ -77,22 +77,22 @@ const firstMatchMessageHandlers: MessageHandlers = {
|
||||
*/
|
||||
const fetchStreamSuggestions = memoize(
|
||||
(query: string, sourcegraphURL?: string): Promise<SearchMatch[]> =>
|
||||
search(
|
||||
of(query),
|
||||
{
|
||||
version: LATEST_VERSION,
|
||||
patternType: SearchPatternType.standard,
|
||||
caseSensitive: false,
|
||||
trace: undefined,
|
||||
sourcegraphURL,
|
||||
},
|
||||
firstMatchMessageHandlers
|
||||
)
|
||||
.pipe(
|
||||
lastValueFrom(
|
||||
search(
|
||||
of(query),
|
||||
{
|
||||
version: LATEST_VERSION,
|
||||
patternType: SearchPatternType.standard,
|
||||
caseSensitive: false,
|
||||
trace: undefined,
|
||||
sourcegraphURL,
|
||||
},
|
||||
firstMatchMessageHandlers
|
||||
).pipe(
|
||||
switchAggregateSearchResults,
|
||||
map(suggestions => suggestions.results)
|
||||
)
|
||||
.toPromise(),
|
||||
),
|
||||
(query, sourcegraphURL) => `${query}|${sourcegraphURL}`
|
||||
)
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import React, { useCallback, useState } from 'react'
|
||||
|
||||
import { lastValueFrom } from 'rxjs'
|
||||
|
||||
import { asError, type ErrorLike } from '@sourcegraph/common'
|
||||
import { gql, dataOrThrowErrors } from '@sourcegraph/http-client'
|
||||
import { Button, Modal, H3, Form } from '@sourcegraph/wildcard'
|
||||
@ -9,16 +11,18 @@ import type { Scalars, DeleteExternalAccountResult, DeleteExternalAccountVariabl
|
||||
|
||||
const deleteUserExternalAccount = async (externalAccount: Scalars['ID']): Promise<void> => {
|
||||
dataOrThrowErrors(
|
||||
await requestGraphQL<DeleteExternalAccountResult, DeleteExternalAccountVariables>(
|
||||
gql`
|
||||
mutation DeleteExternalAccount($externalAccount: ID!) {
|
||||
deleteExternalAccount(externalAccount: $externalAccount) {
|
||||
alwaysNil
|
||||
await lastValueFrom(
|
||||
requestGraphQL<DeleteExternalAccountResult, DeleteExternalAccountVariables>(
|
||||
gql`
|
||||
mutation DeleteExternalAccount($externalAccount: ID!) {
|
||||
deleteExternalAccount(externalAccount: $externalAccount) {
|
||||
alwaysNil
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ externalAccount }
|
||||
).toPromise()
|
||||
`,
|
||||
{ externalAccount }
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { EMPTY, type Observable, Subject } from 'rxjs'
|
||||
import { EMPTY, type Observable, Subject, lastValueFrom } from 'rxjs'
|
||||
import { bufferTime, catchError, concatMap, map } from 'rxjs/operators'
|
||||
|
||||
import { createAggregateError } from '@sourcegraph/common'
|
||||
@ -126,10 +126,11 @@ export const logEventsMutation = gql`
|
||||
`
|
||||
|
||||
function sendEvents(events: Event[]): Promise<void> {
|
||||
return requestGraphQL<LogEventsResult, LogEventsVariables>(logEventsMutation, {
|
||||
events,
|
||||
})
|
||||
.toPromise()
|
||||
return lastValueFrom(
|
||||
requestGraphQL<LogEventsResult, LogEventsVariables>(logEventsMutation, {
|
||||
events,
|
||||
})
|
||||
)
|
||||
.then(dataOrThrowErrors)
|
||||
.then(() => {})
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React, { type FunctionComponent, useMemo, useState } from 'react'
|
||||
|
||||
import classNames from 'classnames'
|
||||
import { lastValueFrom } from 'rxjs'
|
||||
|
||||
import { asError, isErrorLike, type ErrorLike } from '@sourcegraph/common'
|
||||
import { gql, dataOrThrowErrors } from '@sourcegraph/http-client'
|
||||
@ -55,16 +56,18 @@ export const AddUserEmailForm: FunctionComponent<React.PropsWithChildren<Props>>
|
||||
|
||||
try {
|
||||
dataOrThrowErrors(
|
||||
await requestGraphQL<AddUserEmailResult, AddUserEmailVariables>(
|
||||
gql`
|
||||
mutation AddUserEmail($user: ID!, $email: String!) {
|
||||
addUserEmail(user: $user, email: $email) {
|
||||
alwaysNil
|
||||
await lastValueFrom(
|
||||
requestGraphQL<AddUserEmailResult, AddUserEmailVariables>(
|
||||
gql`
|
||||
mutation AddUserEmail($user: ID!, $email: String!) {
|
||||
addUserEmail(user: $user, email: $email) {
|
||||
alwaysNil
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ user: user.id, email: emailState.value }
|
||||
).toPromise()
|
||||
`,
|
||||
{ user: user.id, email: emailState.value }
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
eventLogger.log('NewUserEmailAddressAdded')
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React, { useState, type FunctionComponent, useCallback } from 'react'
|
||||
|
||||
import classNames from 'classnames'
|
||||
import { lastValueFrom } from 'rxjs'
|
||||
|
||||
import { asError, type ErrorLike, isErrorLike } from '@sourcegraph/common'
|
||||
import { gql, dataOrThrowErrors } from '@sourcegraph/http-client'
|
||||
@ -58,16 +59,18 @@ export const SetUserPrimaryEmailForm: FunctionComponent<React.PropsWithChildren<
|
||||
|
||||
try {
|
||||
dataOrThrowErrors(
|
||||
await requestGraphQL<SetUserEmailPrimaryResult, SetUserEmailPrimaryVariables>(
|
||||
gql`
|
||||
mutation SetUserEmailPrimary($user: ID!, $email: String!) {
|
||||
setUserEmailPrimary(user: $user, email: $email) {
|
||||
alwaysNil
|
||||
await lastValueFrom(
|
||||
requestGraphQL<SetUserEmailPrimaryResult, SetUserEmailPrimaryVariables>(
|
||||
gql`
|
||||
mutation SetUserEmailPrimary($user: ID!, $email: String!) {
|
||||
setUserEmailPrimary(user: $user, email: $email) {
|
||||
alwaysNil
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ user: user.id, email: primaryEmail }
|
||||
).toPromise()
|
||||
`,
|
||||
{ user: user.id, email: primaryEmail }
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
eventLogger.log('UserEmailAddressSetAsPrimary')
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { type FunctionComponent, useState, useCallback } from 'react'
|
||||
|
||||
import { lastValueFrom } from 'rxjs'
|
||||
|
||||
import { asError, type ErrorLike } from '@sourcegraph/common'
|
||||
import { dataOrThrowErrors, gql } from '@sourcegraph/http-client'
|
||||
import { Badge, Button, screenReaderAnnounce } from '@sourcegraph/wildcard'
|
||||
@ -35,16 +37,18 @@ export const resendVerificationEmail = async (
|
||||
): Promise<void> => {
|
||||
try {
|
||||
dataOrThrowErrors(
|
||||
await requestGraphQL<ResendVerificationEmailResult, ResendVerificationEmailVariables>(
|
||||
gql`
|
||||
mutation ResendVerificationEmail($userID: ID!, $email: String!) {
|
||||
resendVerificationEmail(user: $userID, email: $email) {
|
||||
alwaysNil
|
||||
await lastValueFrom(
|
||||
requestGraphQL<ResendVerificationEmailResult, ResendVerificationEmailVariables>(
|
||||
gql`
|
||||
mutation ResendVerificationEmail($userID: ID!, $email: String!) {
|
||||
resendVerificationEmail(user: $userID, email: $email) {
|
||||
alwaysNil
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ userID, email }
|
||||
).toPromise()
|
||||
`,
|
||||
{ userID, email }
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
eventLogger.log('UserEmailAddressVerificationResent')
|
||||
@ -79,16 +83,18 @@ export const UserEmail: FunctionComponent<React.PropsWithChildren<Props>> = ({
|
||||
|
||||
try {
|
||||
dataOrThrowErrors(
|
||||
await requestGraphQL<RemoveUserEmailResult, RemoveUserEmailVariables>(
|
||||
gql`
|
||||
mutation RemoveUserEmail($user: ID!, $email: String!) {
|
||||
removeUserEmail(user: $user, email: $email) {
|
||||
alwaysNil
|
||||
await lastValueFrom(
|
||||
requestGraphQL<RemoveUserEmailResult, RemoveUserEmailVariables>(
|
||||
gql`
|
||||
mutation RemoveUserEmail($user: ID!, $email: String!) {
|
||||
removeUserEmail(user: $user, email: $email) {
|
||||
alwaysNil
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ user, email }
|
||||
).toPromise()
|
||||
`,
|
||||
{ user, email }
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
setIsLoading(false)
|
||||
@ -108,16 +114,18 @@ export const UserEmail: FunctionComponent<React.PropsWithChildren<Props>> = ({
|
||||
|
||||
try {
|
||||
dataOrThrowErrors(
|
||||
await requestGraphQL<SetUserEmailVerifiedResult, SetUserEmailVerifiedVariables>(
|
||||
gql`
|
||||
mutation SetUserEmailVerified($user: ID!, $email: String!, $verified: Boolean!) {
|
||||
setUserEmailVerified(user: $user, email: $email, verified: $verified) {
|
||||
alwaysNil
|
||||
await lastValueFrom(
|
||||
requestGraphQL<SetUserEmailVerifiedResult, SetUserEmailVerifiedVariables>(
|
||||
gql`
|
||||
mutation SetUserEmailVerified($user: ID!, $email: String!, $verified: Boolean!) {
|
||||
setUserEmailVerified(user: $user, email: $email, verified: $verified) {
|
||||
alwaysNil
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ user, email, verified }
|
||||
).toPromise()
|
||||
`,
|
||||
{ user, email, verified }
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
setIsLoading(false)
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React, { type FunctionComponent, useEffect, useState, useCallback } from 'react'
|
||||
|
||||
import classNames from 'classnames'
|
||||
import { lastValueFrom } from 'rxjs'
|
||||
|
||||
import { asError, type ErrorLike, isErrorLike } from '@sourcegraph/common'
|
||||
import { gql, dataOrThrowErrors, useQuery } from '@sourcegraph/http-client'
|
||||
@ -142,27 +143,29 @@ export const UserSettingsEmailsPage: FunctionComponent<React.PropsWithChildren<P
|
||||
|
||||
async function fetchUserEmails(userID: Scalars['ID']): Promise<UserEmailsResult> {
|
||||
return dataOrThrowErrors(
|
||||
await requestGraphQL<UserEmailsResult, UserEmailsVariables>(
|
||||
gql`
|
||||
fragment UserEmail on UserEmail {
|
||||
email
|
||||
isPrimary
|
||||
verified
|
||||
verificationPending
|
||||
viewerCanManuallyVerify
|
||||
}
|
||||
query UserEmails($user: ID!) {
|
||||
node(id: $user) {
|
||||
... on User {
|
||||
__typename
|
||||
emails {
|
||||
...UserEmail
|
||||
await lastValueFrom(
|
||||
requestGraphQL<UserEmailsResult, UserEmailsVariables>(
|
||||
gql`
|
||||
fragment UserEmail on UserEmail {
|
||||
email
|
||||
isPrimary
|
||||
verified
|
||||
verificationPending
|
||||
viewerCanManuallyVerify
|
||||
}
|
||||
query UserEmails($user: ID!) {
|
||||
node(id: $user) {
|
||||
... on User {
|
||||
__typename
|
||||
emails {
|
||||
...UserEmail
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ user: userID }
|
||||
).toPromise()
|
||||
`,
|
||||
{ user: userID }
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -396,7 +396,7 @@
|
||||
"recharts": "^1.8.5",
|
||||
"regexpp": "^3.1.0",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"rxjs": "^6.6.3",
|
||||
"rxjs": "^7.8.1",
|
||||
"semver": "^7.3.2",
|
||||
"stream-http": "^3.2.0",
|
||||
"stream-json": "^1.7.5",
|
||||
|
||||
@ -407,8 +407,8 @@ importers:
|
||||
specifier: ^1.5.1
|
||||
version: 1.5.1
|
||||
rxjs:
|
||||
specifier: ^6.6.3
|
||||
version: 6.6.7
|
||||
specifier: ^7.8.1
|
||||
version: 7.8.1
|
||||
semver:
|
||||
specifier: ^7.3.2
|
||||
version: 7.6.0
|
||||
@ -22898,6 +22898,7 @@ packages:
|
||||
engines: {npm: '>=2.0.0'}
|
||||
dependencies:
|
||||
tslib: 2.1.0
|
||||
dev: true
|
||||
|
||||
/rxjs@7.8.1:
|
||||
resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user