Skip to content
This repository was archived by the owner on Dec 31, 2023. It is now read-only.

Commit 3119aa5

Browse files
committed
Create command to open file in phabricator instance, specified by default in ~/.arcrc
0 parents  commit 3119aa5

File tree

8 files changed

+691
-0
lines changed

8 files changed

+691
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.DS_Store
2+
npm-debug.log
3+
node_modules

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## 1.0.0 - First Release
2+
* Creates Open In Diffusion command that reads from ~/.arcrc to find and query your phabricator/diffusion installation.
3+
* Creates Clear Cache command to reset session history

LICENSE.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Copyright (c) 2017 <Your name here>
2+
3+
Permission is hereby granted, free of charge, to any person obtaining
4+
a copy of this software and associated documentation files (the
5+
"Software"), to deal in the Software without restriction, including
6+
without limitation the rights to use, copy, modify, merge, publish,
7+
distribute, sublicense, and/or sell copies of the Software, and to
8+
permit persons to whom the Software is furnished to do so, subject to
9+
the following conditions:
10+
11+
The above copyright notice and this permission notice shall be
12+
included in all copies or substantial portions of the Software.
13+
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# open-in-diffusion package
2+
3+
Open the current file in the Phabricator/Diffusion web UI.
4+
5+
Reads `~/.arcrc` by default for conduit credentials. Run `arc install-certificate` to get setup, or manually enter your conduit host/token information in the `Settings`.

index.js

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
'use babel';
2+
3+
import path from 'path';
4+
import { CompositeDisposable } from 'atom';
5+
import opn from 'opn';
6+
import Canduit from 'canduit';
7+
8+
const CONFIG_DEFAULT_HOST = 'https://phabricator.example.com/api/';
9+
const CONFIG_DEFAULT_TOKEN = 'api-XYZ';
10+
11+
function rangeToString(range) {
12+
if (range.start.row === range.end.row) {
13+
return range.start.row + 1;
14+
} else {
15+
return (range.start.row + 1) + '-' + (range.end.row + 1);
16+
}
17+
}
18+
19+
function simplifyProjectName(name) {
20+
return (name || '').trim().toLowerCase().replace(/[\s\-\_\(\)]/g, '');
21+
}
22+
23+
export default {
24+
subscriptions: null,
25+
canduit: null,
26+
foundProjects: {},
27+
28+
config: {
29+
'conduit-host': {
30+
title: 'Optional: Conduit API endpoint',
31+
description: 'URL to the phabricator instance API, usually ends with `/api/`',
32+
type: 'string',
33+
default: CONFIG_DEFAULT_HOST,
34+
},
35+
'conduit-token': {
36+
title: 'Optional: Conduit API Token',
37+
description: 'Get a Standard API Token from https://phabricator.example.com/settings and clicking on "Conduit API Tokens".',
38+
type: 'string',
39+
default: CONFIG_DEFAULT_TOKEN,
40+
},
41+
},
42+
43+
activate(state) {
44+
this.subscriptions = new CompositeDisposable();
45+
46+
this.subscriptions.add(
47+
atom.config.onDidChange('open-in-diffusion.conduit-host', () => {
48+
this.connectToConduit();
49+
})
50+
);
51+
52+
this.subscriptions.add(
53+
atom.config.onDidChange('open-in-diffusion.conduit-token', () => {
54+
this.connectToConduit();
55+
})
56+
);
57+
58+
this.subscriptions.add(atom.commands.add('atom-workspace', {
59+
'open-in-diffusion:clear-project-cache': () => {
60+
this.foundProjects = {};
61+
},
62+
'open-in-diffusion:open-in-phabricator': () => {
63+
const editor = atom.workspace.getActiveTextEditor();
64+
if (!editor) {
65+
return;
66+
}
67+
68+
this.openInDiffusion(
69+
editor.getPath(),
70+
editor.getSelectedBufferRanges()
71+
);
72+
},
73+
}));
74+
75+
this.connectToConduit();
76+
},
77+
78+
deactivate() {
79+
this.subscriptions.dispose();
80+
},
81+
82+
serialize() {
83+
return {};
84+
},
85+
86+
conduitConfig() {
87+
const host = atom.config.get('open-in-diffusion.conduit-host');
88+
const token = atom.config.get('open-in-diffusion.conduit-token');
89+
if (host === CONFIG_DEFAULT_HOST || token === CONFIG_DEFAULT_TOKEN) {
90+
return {};
91+
} else {
92+
return {api: host, token: token};
93+
}
94+
},
95+
96+
conduitFactory() {
97+
return new Promise((resolve, reject) => {
98+
new Canduit(this.conduitConfig(), (err, canduit) => {
99+
if (err) {
100+
reject(err);
101+
}
102+
resolve(canduit);
103+
});
104+
});
105+
},
106+
107+
connectToConduit() {
108+
this.canduit = null;
109+
this.conduitFactory()
110+
.then((canduit) => {
111+
this.canduit = canduit;
112+
console.info(`Connected to diffusion.`);
113+
});
114+
},
115+
116+
genRepositorySearch(search) {
117+
return this.genExecPhabricatorQuery('diffusion.repository.search', {
118+
queryKey: ['active'],
119+
constraints: {
120+
query: search,
121+
},
122+
});
123+
},
124+
125+
genExecPhabricatorQuery(endpoint, options) {
126+
return new Promise((resolve, reject) => {
127+
if (!this.canduit) {
128+
reject('Not yet connected to canduit');
129+
}
130+
this.canduit.exec(endpoint, options, (err, response) => {
131+
if (err) {
132+
reject(err);
133+
return;
134+
}
135+
resolve(response);
136+
});
137+
});
138+
},
139+
140+
genFindPhabProject(projectPath) {
141+
const searchTerm = path.basename(projectPath);
142+
143+
if (this.foundProjects[projectPath]) {
144+
return Promise.resolve(this.foundProjects[projectPath]);
145+
} else {
146+
return this.genRepositorySearch(searchTerm)
147+
.then((response) => {
148+
this.foundProjects[projectPath] = this.getMatchingPhabProject(response.data, searchTerm);
149+
return this.foundProjects[projectPath];
150+
});
151+
}
152+
},
153+
154+
getMatchingPhabProject(data, searchTerm) {
155+
const simpleSearchTerm = simplifyProjectName(searchTerm);
156+
157+
const names = data.map((data) => {
158+
if (!data.fields) {
159+
return null;
160+
}
161+
return {
162+
name: simplifyProjectName(data.fields.name),
163+
shortName: simplifyProjectName(data.fields.shortName),
164+
callsign: simplifyProjectName(data.fields.callsign),
165+
project: data,
166+
};
167+
}).filter(Boolean);
168+
169+
const nameMatches = names.filter((item) => item.name === simpleSearchTerm);
170+
if (nameMatches.length) {
171+
return nameMatches.shift().project;
172+
}
173+
const shortNameMatches = names.filter((item) => item.shortName === simpleSearchTerm);
174+
if (shortNameMatches.length) {
175+
return shortNameMatches.shift().project;
176+
}
177+
const callSignMatches = names.filter((item) => item.callsign === simpleSearchTerm);
178+
if (callSignMatches.length) {
179+
return callSignMatches.shift().project;
180+
}
181+
182+
throw Error(`No project found matching ${searchTerm}`);
183+
},
184+
185+
getProjectPath(filePath) {
186+
return atom.project.getPaths()
187+
.filter((path) => filePath.startsWith(path))
188+
.shift();
189+
},
190+
191+
openInDiffusion(nuclideFilePath, selectedRanges) {
192+
const projectPath = this.getProjectPath(nuclideFilePath);
193+
const relativeFilePath = nuclideFilePath.replace(projectPath, '');
194+
195+
const range = '$' + selectedRanges
196+
.map(rangeToString)
197+
.join(',');
198+
199+
this.genFindPhabProject(projectPath)
200+
.then((project) => {
201+
const id = project.fields.callsign
202+
? project.fields.callsign
203+
: project.id;
204+
205+
opn(`https://phabricator.pinadmin.com/diffusion/${id}/browse/master${relativeFilePath}${range}`);
206+
})
207+
.catch((message) => {
208+
console.warn(`Unable to open ${nuclideFilePath}. ${message}`);
209+
});
210+
},
211+
212+
};

menus/open-in-diffusion.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"context-menu": {
3+
"atom-text-editor": [
4+
{
5+
"label": "Open In Diffusion",
6+
"command": "open-in-diffusion:open-in-phabricator"
7+
}
8+
]
9+
},
10+
"menu": [
11+
{
12+
"label": "Packages",
13+
"submenu": [
14+
{
15+
"label": "Phabricator",
16+
"submenu": [
17+
{
18+
"label": "Open In Diffusion",
19+
"command": "open-in-diffusion:open-in-phabricator"
20+
},
21+
{
22+
"label": "Clear cache",
23+
"command": "open-in-diffusion:clear-project-cache"
24+
}
25+
]
26+
}
27+
]
28+
}
29+
]
30+
}

0 commit comments

Comments
 (0)