This file cannot be previewed or downloaded.
;
78 | }
79 |
80 | return fileContainerHTML;
81 | }
--------------------------------------------------------------------------------
/SearchUI/src/components/Facets/CheckboxFacet/CheckboxFacet.css:
--------------------------------------------------------------------------------
1 | .facet-checkbox {
2 | list-style-type: none;
3 | padding: 0;
4 | margin: 0;
5 | }
6 |
7 | .card-body {
8 | padding-left: 0px;
9 | }
10 |
11 | .facet-header:hover {
12 | text-decoration: underline;
13 | cursor: pointer;
14 | }
15 |
--------------------------------------------------------------------------------
/SearchUI/src/components/Facets/CheckboxFacet/CheckboxFacet.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT license.
3 |
4 | import React, {useState} from 'react';
5 | import { Collapse, Checkbox, List, ListItem, ListItemText } from '@material-ui/core';
6 | import { ExpandLess, ExpandMore } from '@material-ui/icons';
7 | import styled from 'styled-components';
8 |
9 | import './CheckboxFacet.css';
10 |
11 | export default function CheckboxFacet(props) {
12 |
13 | let [isExpanded, setIsExpanded] = useState(false);
14 |
15 | const checkboxes = props.values.map(facetValue => {
16 |
17 | let isSelected = props.selectedFacets.some(facet => facet.value === facetValue.value);
18 |
19 | return (
20 | f.field === key)}
43 | />;
44 | });
45 | } catch (error) {
46 | console.log(error);
47 | }
48 |
49 | const filters = props.filters.map((filter, index) => {
50 | return (
51 |
52 | removeFilter(filter)}
55 | className="chip"
56 | />
57 | );
58 | });
59 |
60 |
61 | return (
62 |
63 |
64 |
69 |
70 | {facets}
71 |
72 |
73 |
74 | );
75 | };
76 |
77 | const FacetList = styled(List)({
78 | marginTop: '32px !important'
79 | })
80 |
--------------------------------------------------------------------------------
/SearchUI/src/components/Pager/Pager.css:
--------------------------------------------------------------------------------
1 | .item {
2 | margin: 1em auto;
3 | }
4 |
5 | .pager {
6 | margin: auto;
7 | max-width: fit-content;
8 | }
--------------------------------------------------------------------------------
/SearchUI/src/components/Pager/Pager.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT license.
3 |
4 | import React, {useState, useEffect} from 'react';
5 |
6 | import './Pager.css';
7 |
8 | export default function Pager(props) {
9 |
10 | let [selectedPage, setSelectedPage] = useState(props.currentPage);
11 | let totalPages = Math.ceil(props.resultCount / props.resultsPerPage);
12 |
13 | useEffect(_=>{
14 | props.setCurrentPage(selectedPage);
15 | }, [selectedPage, props]);
16 |
17 | function goToNextPage() {
18 | setSelectedPage(selectedPage + 1);
19 | }
20 |
21 | function goToPreviousPage() {
22 | setSelectedPage(selectedPage - 1);
23 | }
24 |
25 | var i = 0;
26 | var page_links = [];
27 |
28 | var minPage = 1;
29 | var maxPage = totalPages;
30 |
31 | if (selectedPage - minPage > 2) {
32 | minPage = selectedPage - 2;
33 | }
34 |
35 | if (maxPage - selectedPage > 2) {
36 | maxPage = parseInt(selectedPage) + 2;
37 | }
38 |
39 |
40 | for (i = minPage; i <= maxPage; i++) {
41 | if (i === parseInt(selectedPage)) {
42 | page_links.push(
43 |
44 |
45 | {i}
46 |
47 |
48 | );
49 | } else {
50 | page_links.push(
51 |
52 |
53 |
54 | );
55 | }
56 | }
57 |
58 | var previousButton;
59 | if (parseInt(selectedPage) === 1) {
60 | previousButton = (
61 | Previous
62 | );
63 | } else {
64 | previousButton = (
65 |
66 | );
67 | }
68 |
69 | var nextButton;
70 | if (parseInt(selectedPage) === totalPages) {
71 | nextButton = (
72 | Next
73 | );
74 | } else {
75 | nextButton = (
76 |
77 | );
78 | }
79 |
80 |
81 |
82 | return (
83 |
90 | );
91 |
92 | }
--------------------------------------------------------------------------------
/SearchUI/src/components/Results/Answer/Answer.css:
--------------------------------------------------------------------------------
1 | .answer {
2 |
3 | padding: 16px;
4 | text-align: left;
5 | border: 1px solid #eee;
6 | box-shadow: 0 2px 3px #ccc;
7 | margin: 10px;
8 | margin-bottom: 3px;
9 | padding-bottom: 3px;
10 | box-sizing: border-box;
11 | overflow: hidden;
12 | /* cursor: pointer; */
13 | }
14 | /*
15 | .result:hover,
16 | .result:active {
17 | background-color: #C0DDF5;
18 | } */
19 |
20 | .result-item {
21 | margin-bottom: 2em;
22 | text-align:left;
23 | }
24 |
25 | .title-style {
26 | vertical-align: left;
27 | }
28 |
29 | em {
30 | font-weight: bold;
31 | font-style: normal;
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/SearchUI/src/components/Results/Answer/Answer.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT license.
3 |
4 | import React from 'react';
5 | import ReactMarkdown from 'react-markdown';
6 |
7 | import './Answer.css';
8 |
9 |
10 | export default function Answer(props) {
11 |
12 | const bodyStyle = {
13 | padding: '0.25rem',
14 | overflowWrap: 'normal'
15 | };
16 |
17 | const pStyle = {
18 | paddingLeft: '0.25rem',
19 | fontSize: '0.9rem'
20 | };
21 |
22 | const uriStyle = {
23 | color: 'green',
24 | paddingLeft: '0.25rem',
25 | paddingTop: '0',
26 | paddingBottom: '0',
27 | marginTop: '0',
28 | marginBottom: '0',
29 | whiteSpace: 'nowrap',
30 | overflow: 'hidden'
31 | };
32 |
33 | return (
34 |
49 | );
50 | }
51 |
--------------------------------------------------------------------------------
/SearchUI/src/components/Results/Result/Result.css:
--------------------------------------------------------------------------------
1 | .result {
2 | padding: 8px;
3 | padding-left: 8px;
4 | text-align: left;
5 | margin: 10px;
6 | margin-bottom: 3px;
7 | padding-bottom: 0px;
8 | /* cursor: pointer; */
9 | }
10 | /*
11 | .result:hover,
12 | .result:active {
13 | background-color: #C0DDF5;
14 | } */
15 |
16 | .result-item {
17 | margin-bottom: 2em;
18 | text-align:left;
19 | }
20 |
21 | .title-style {
22 | vertical-align: left;
23 | }
24 |
25 | em {
26 | font-weight: bold;
27 | font-style: normal;
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/SearchUI/src/components/Results/Result/Result.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT license.
3 |
4 | import React from 'react'
5 | import ReactHtmlParser from 'react-html-parser';
6 |
7 | import './Result.css';
8 |
9 | export default function Result(props) {
10 |
11 | const cardStyle = {
12 | maxHeight: '18rem',
13 | display: 'block'
14 | };
15 |
16 | const bodyStyle = {
17 | padding: '0.25rem',
18 | paddingBottom: '0',
19 | marginBottom: '0'
20 | };
21 |
22 | const pStyle = {
23 | paddingLeft: '0.25rem',
24 | fontSize: '0.9rem'
25 | };
26 |
27 | const uriStyle = {
28 | color: 'green',
29 | paddingLeft: '0.25rem',
30 | paddingTop: '0',
31 | paddingBottom: '0',
32 | marginTop: '0',
33 | marginBottom: '0',
34 | whiteSpace: 'nowrap',
35 | overflow: 'hidden'
36 | };
37 |
38 | return (
39 |
50 | );
51 | }
52 |
--------------------------------------------------------------------------------
/SearchUI/src/components/Results/Results.css:
--------------------------------------------------------------------------------
1 | /* Detail Styles */
2 | .result-item {
3 | margin-bottom: 2em;
4 | text-align:center;
5 | }
6 | .result-item-image {
7 | min-width: 150px;
8 | max-width: 200px;
9 | max-height: 300px;
10 | }
11 |
12 | .Results {
13 | display: flex;
14 | flex-flow: row wrap;
15 | justify-content: center;
16 | width: 100%;
17 | margin: auto;
18 | margin-left: 0em;
19 | margin-right: 0em;
20 | }
--------------------------------------------------------------------------------
/SearchUI/src/components/Results/Results.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT license.
3 |
4 | import React from 'react';
5 | import Result from './Result/Result';
6 | import Answer from './Answer/Answer';
7 |
8 | import "./Results.css";
9 |
10 | export default function Results(props) {
11 |
12 | const infoStyle = {
13 | margin: '1em'
14 | }
15 |
16 | let results = props.documents.map((result, index) => {
17 | return ;
22 | });
23 |
24 | let beginDocNumber = Math.min(props.skip + 1, props.count);
25 | let endDocNumber = Math.min(props.skip + props.top, props.count);
26 |
27 | //console.log(props.documents);
28 | var answer;
29 | if(props.answer?.answer?.answer && beginDocNumber === 1 && props.answer?.answer?.answer !== "No good match found in KB.") {
30 | answer = ;
31 | } else {
32 | answer = null;
33 | }
34 |
35 | return (
36 |
37 |
Showing {beginDocNumber}-{endDocNumber} of {props.count.toLocaleString()} results
38 |
39 | {answer}
40 |
41 |
42 | {results}
43 |
44 |
45 | );
46 | };
47 |
--------------------------------------------------------------------------------
/SearchUI/src/components/SearchBar/SearchBar.css:
--------------------------------------------------------------------------------
1 | div.container a.input-group-btn {
2 | font-size: 14px;
3 | }
4 |
5 | .suggestions {
6 | position: relative;
7 | display: inline-block;
8 | width: inherit;
9 | z-index:99
10 | }
11 |
12 | .input-group {
13 | flex-wrap: nowrap;
14 | }
--------------------------------------------------------------------------------
/SearchUI/src/components/SearchBar/SearchBar.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT license.
3 |
4 | import React, {useState, useEffect} from 'react';
5 | import axios from 'axios';
6 | import Suggestions from './Suggestions/Suggestions';
7 |
8 | import "./SearchBar.css";
9 |
10 | export default function SearchBar(props) {
11 |
12 | let [q, setQ] = useState("");
13 | let [suggestions, setSuggestions] = useState([]);
14 | let [showSuggestions, setShowSuggestions] = useState(false);
15 |
16 | const onSearchHandler = () => {
17 | props.postSearchHandler(q);
18 | setShowSuggestions(false);
19 | }
20 |
21 | const onEnterButton = (event) => {
22 | if (event.keyCode === 13) {
23 | onSearchHandler();
24 | }
25 | }
26 |
27 | const suggestionClickHandler = (s) => {
28 | document.getElementById("search-box").value = s;
29 | setShowSuggestions(false);
30 | props.postSearchHandler(s);
31 |
32 | }
33 |
34 | const onChangeHandler = () => {
35 | var searchTerm = document.getElementById("search-box").value;
36 | setShowSuggestions(true);
37 | setQ(searchTerm);
38 |
39 | // use this prop if you want to make the search more reactive
40 | if (props.searchChangeHandler) {
41 | props.searchChangeHandler(searchTerm);
42 | }
43 | }
44 |
45 | useEffect(_ =>{
46 | const timer = setTimeout(() => {
47 | const body = {
48 | q: q,
49 | top: 5,
50 | suggester: 'sg'
51 | };
52 |
53 | const headers = {
54 | "x-functions-key": props.code
55 | };
56 |
57 | if (q === '') {
58 | setSuggestions([]);
59 | } else {
60 | const url = props.url + '/api/suggest';
61 | axios.post( url, body, {headers: headers})
62 | .then( response => {
63 | setSuggestions(response.data.suggestions);
64 | } )
65 | .catch(error => {
66 | console.log(error);
67 | setSuggestions([]);
68 | });
69 | }
70 | }, 300);
71 | return () => clearTimeout(timer);
72 | }, [q, props]);
73 |
74 | var suggestionDiv;
75 | if (showSuggestions) {
76 | suggestionDiv = ( suggestionClickHandler(s)}>);
77 | } else {
78 | suggestionDiv = ();
79 | }
80 |
81 | return (
82 |
83 |
onEnterButton(e)}>
84 |
85 | setShowSuggestions(false)}
94 | onClick={() => setShowSuggestions(true)}>
95 |
96 | {suggestionDiv}
97 |
98 |
99 |
102 |
103 |
104 |
105 | );
106 | };
--------------------------------------------------------------------------------
/SearchUI/src/components/SearchBar/Suggestions/Suggestions.css:
--------------------------------------------------------------------------------
1 | .suggestion-item:hover {
2 | /*when hovering an item:*/
3 | background-color: #e9e9e9;
4 | cursor: pointer;
5 | }
6 |
7 |
--------------------------------------------------------------------------------
/SearchUI/src/components/SearchBar/Suggestions/Suggestions.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT license.
3 |
4 | import React from 'react';
5 |
6 | import "./Suggestions.css";
7 | import 'bootstrap/dist/css/bootstrap.min.css';
8 |
9 | export default function Suggestions(props) {
10 |
11 | const suggestionClickHandler = (e) => {
12 | props.suggestionClickHandler(e.currentTarget.id);
13 | }
14 |
15 | const borders = {
16 | border: "1px solid #eee",
17 | boxShadow: "0 2px 3px #ccc",
18 | boxSizing: "border-box"
19 | }
20 |
21 | let suggestions = props.suggestions.map((s, index) => {
22 | return ({s.queryPlusText}
);
23 | });
24 |
25 | return (
26 |
27 | {suggestions}
28 |
29 | );
30 | };
31 |
--------------------------------------------------------------------------------
/SearchUI/src/components/Transcript/Transcript.css:
--------------------------------------------------------------------------------
1 | pre {
2 | font-family: 'Segoe UI', sans-serif;
3 | font-size: 14px;
4 | white-space: pre-line;
5 | word-break: keep-all;
6 | }
7 |
8 | .scroll {
9 | overflow-y: auto;
10 | }
11 |
12 | .highlight {
13 | background-color: rgb(160, 197, 232);
14 | }
15 |
--------------------------------------------------------------------------------
/SearchUI/src/components/Transcript/Transcript.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT license.
3 |
4 | import React, {useEffect} from 'react'
5 | import ReactHtmlParser from 'react-html-parser';
6 |
7 | import './Transcript.css';
8 |
9 |
10 | export default function Transcript(props) {
11 |
12 |
13 | useEffect(_ =>{
14 | console.log(props.highlight);
15 | if (!!props.highlight) {
16 |
17 | let highlightedElement = document.getElementById(props.highlight)
18 | if (!!highlightedElement) {
19 | highlightedElement.scrollIntoView({block: 'start', behavior: 'smooth'});
20 | }
21 | }
22 | }, [props]);
23 |
24 |
25 | let full_content = "";
26 |
27 | // If we have merged content, let's use it.
28 | if (props.document.merged_content) {
29 | if (props.document.merged_content.length > 0) {
30 | full_content = props.document.merged_content.trim();
31 | }
32 | }
33 | else {
34 | // otherwise, let's try getting the content -- although it won't have any image data.
35 | full_content = props.document.content.trim();
36 | }
37 |
38 | if (full_content === null || full_content === "") {
39 | // not much to display
40 | return null;
41 | }
42 |
43 | // finds all matches to the search term in the transcript and adds a highlight class to them (plus an id that can be used for scrolling)
44 | function GetReferences(searchText, content) {
45 | // find all matches in content
46 | var regex = new RegExp(searchText, 'gi');
47 |
48 | var i = -1;
49 | var response = content.replace(regex, function (str) {
50 | i++;
51 | var shortname = str.slice(0, 20).replace(/[^a-zA-Z ]/g, " ").replace(new RegExp(" ", 'g'), "_");
52 | return `${str}`;
53 | })
54 |
55 | return response;
56 | }
57 |
58 |
59 | if (props.q.trim() !== "") {
60 | full_content = GetReferences(props.q, full_content);
61 | }
62 |
63 |
64 | return (
65 |
66 |
67 |
68 |
69 |
70 | {ReactHtmlParser(full_content)}
71 | |
72 |
73 |
74 |
75 |
76 | );
77 | }
--------------------------------------------------------------------------------
/SearchUI/src/contexts/AuthContext.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT license.
3 |
4 | import { createContext, useContext } from 'react';
5 |
6 | // Create new auth context
7 | export const AuthContext = createContext();
8 |
9 | // React hook to use auth context
10 | export function useAuth() {
11 | return useContext(AuthContext);
12 | }
13 |
--------------------------------------------------------------------------------
/SearchUI/src/index.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT license.
3 |
4 | import React from 'react';
5 | import ReactDOM from 'react-dom';
6 |
7 | import App from './App/App';
8 |
9 | ReactDOM.render(
10 |
11 |
12 | ,
13 | document.getElementById('root')
14 | );
15 |
--------------------------------------------------------------------------------
/SearchUI/src/pages/Details/Details.css:
--------------------------------------------------------------------------------
1 | .main--details {
2 | padding-top: 1em;
3 | width: 100%;
4 | height: 100%;
5 | };
6 |
7 | .image {
8 | width: 10em;
9 | height: auto;
10 | }
11 |
12 | .nav-tabs {
13 | margin-left: 1em;
14 |
15 | }
16 |
17 | .black {
18 | color: black;
19 | }
20 |
21 | .black:hover {
22 | color: darkgray;
23 | }
24 |
25 | .result-container {
26 | min-height: 40em;
27 | margin: 10px;
28 | border: 1px solid #ccc;
29 | max-height: 100%;
30 | }
31 |
32 | #tags-panel {
33 | margin-top: 1em;
34 | }
35 |
36 | .tag-container {
37 | display: flex;
38 | flex-flow: row wrap;
39 | justify-content: flex-start;
40 | width: 100%;
41 | margin-top: 1em;
42 | }
43 |
44 | .tag {
45 | background-color: #0078d7;
46 | border: 1px solid transparent;
47 | border-radius: 0;
48 | color: #fff;
49 | display: inline-block;
50 | font-size: 13px;
51 | line-height: 1;
52 | margin: 2px 2px 2px 0;
53 | max-width: 100%;
54 | opacity: 1;
55 | padding: .4em .5em;
56 | position: relative;
57 | text-align: center;
58 | text-decoration: none;
59 | transition: all .15s ease-in-out;
60 | white-space: nowrap;
61 | }
62 |
--------------------------------------------------------------------------------
/SearchUI/src/pages/Details/Details.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT license.
3 |
4 | import React, { useState, useEffect, useRef } from "react";
5 | import { useParams } from 'react-router-dom';
6 | import CircularProgress from '@material-ui/core/CircularProgress';
7 | import Transcript from '../../components/Transcript/Transcript';
8 | import DocumentViewer from '../../components/DocumentViewer/DocumentViewer';
9 | import ReactHtmlParser from 'react-html-parser';
10 | import axios from 'axios';
11 | import "./Details.css";
12 |
13 | export default function Details(props) {
14 |
15 | let { id } = useParams();
16 | const [document, setDocument] = useState({});
17 | const [sasToken, setSasToken] = useState("");
18 | const [selectedTab, setTab] = useState(0);
19 | const [highlight, setHighlight] = useState(null);
20 | const [isLoading, setIsLoading] = useState(true);
21 | const [q, setQ] = useState("");
22 | const searchBar = useRef(null);
23 |
24 | useEffect(() => {
25 | setIsLoading(true);
26 |
27 | const headers = {
28 | "x-functions-key": props.code
29 | };
30 |
31 | const url = props.url + '/api/lookup?id=' + id;
32 | console.log(url);
33 | axios.get(url, {headers: headers})
34 | .then(response => {
35 | const doc = response.data.document;
36 | const sas = response.data.sasToken;
37 | setDocument(doc);
38 | setSasToken(sas);
39 | setIsLoading(false);
40 | })
41 | .catch(error => {
42 | console.log(error);
43 | setIsLoading(false);
44 | });
45 |
46 | }, [id]);
47 |
48 | useEffect(() => {
49 | setHighlight(null);
50 | }, [q]);
51 |
52 | function GetTagsHTML(tags) {
53 |
54 | if (!!tags) {
55 | let tagsHtml = tags.map((tagValue, index) => {
56 | if (index < 10) {
57 |
58 | if (tagValue.length > 30) { // check tag name length
59 | // create substring of tag name length if too long
60 | tagValue = tagValue.slice(0, 30);
61 | }
62 |
63 | return ;
64 | } else {
65 | return null;
66 | }
67 | });
68 |
69 | return tagsHtml;
70 | }
71 |
72 | return null;
73 | }
74 |
75 | let tags = GetTagsHTML(document.keyPhrases);
76 |
77 | function GetSnippets(q, content) {
78 | if (!!content && q.trim() !== "") {
79 | var regex = new RegExp(q, 'gi');
80 |
81 | let matches = content.match(regex);
82 |
83 | return matches.map((value, index) => {
84 | var startIdx;
85 | var maxLengthOfSnippet = 400;
86 | var ln = maxLengthOfSnippet;
87 |
88 | if (value.length > 150) {
89 | startIdx = content.indexOf(value);
90 | ln = value.length;
91 | }
92 | else {
93 | if (content.indexOf(value) < (maxLengthOfSnippet / 2)) {
94 | startIdx = 0;
95 | }
96 | else {
97 | startIdx = content.indexOf(value) - (maxLengthOfSnippet / 2);
98 | }
99 |
100 | ln = maxLengthOfSnippet + value.length;
101 | }
102 |
103 | var reference = content.slice(startIdx, startIdx + ln);
104 | content = content.replace(value, "");
105 |
106 | reference = reference.replace(value, function (str) {
107 | return (`${str}`);
108 | });
109 |
110 | var shortName = value.slice(0, 20).replace(/[^a-zA-Z ]/g, " ").replace(new RegExp(" ", 'g'), "_");
111 |
112 | return ClickSnippet(`${index}_${shortName}`)}>{ReactHtmlParser(reference)};
113 |
114 | });
115 | }
116 | }
117 |
118 | let snippets = GetSnippets(q, document.content);
119 |
120 | function ClickSnippet(name) {
121 | // navigating to the transcript
122 | setTab(1);
123 | setHighlight(name);
124 | }
125 |
126 | var body;
127 | let tab_0_style = "nav-link black";
128 | let tab_1_style = "nav-link black";
129 | let tab_2_style = "nav-link black";
130 | if (isLoading) {
131 | body = ();
132 | } else {
133 | if (selectedTab === 0) {
134 | body = ();
135 | tab_0_style = "nav-link active black";
136 | }
137 | else if (selectedTab === 1) {
138 | body = ();
139 | tab_1_style = "nav-link active black";
140 | }
141 | else if (selectedTab === 2) {
142 | body =
143 |
144 | {JSON.stringify(document, null, 2)}
145 |
146 |
;
147 | tab_2_style = "nav-link active black";
148 | }
149 |
150 | }
151 |
152 |
153 |
154 | return (
155 |
156 |
157 |
158 |
159 | -
160 |
161 |
162 | -
163 |
164 |
165 | -
166 |
167 |
168 |
169 |
170 |
171 |
172 | {body}
173 |
174 |
175 |
204 |
205 |
206 |
207 | );
208 | }
209 |
210 |
--------------------------------------------------------------------------------
/SearchUI/src/pages/Home/Home.css:
--------------------------------------------------------------------------------
1 | .home-search {
2 | margin: 5em auto;
3 | max-width: 45%;
4 | display: block;
5 | }
6 |
7 | .logo {
8 | height: 12em;
9 | width: auto;
10 | display:block;
11 | margin: auto auto 0;
12 | }
13 |
14 | .poweredby {
15 | text-align: center;
16 | }
--------------------------------------------------------------------------------
/SearchUI/src/pages/Home/Home.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT license.
3 |
4 | import React from "react";
5 | import { useHistory } from "react-router-dom";
6 |
7 | import SearchBar from '../../components/SearchBar/SearchBar';
8 |
9 | import "./Home.css";
10 | import "../../pages/Search/Search.css";
11 |
12 | export default function Home(props) {
13 | const history = useHistory();
14 | const navigateToSearchPage = (q) => {
15 | if (!q || q === '') {
16 | q = '*'
17 | }
18 | history.push('/search?q=' + q);
19 | }
20 |
21 | return (
22 |
23 |
24 |

25 |
Powered by Azure Cognitive Search and QnA Maker
26 |
27 |
28 |
29 | );
30 | };
31 |
--------------------------------------------------------------------------------
/SearchUI/src/pages/Search/Search.css:
--------------------------------------------------------------------------------
1 | .sui-layout-header {
2 | background-color: #0078d7;
3 | color: #eee;
4 | }
5 | .sui-search-box__submit {
6 | background: linear-gradient(rgb(60, 226, 102), rgb(34, 151, 57));
7 | letter-spacing: 0.1em;
8 | }
9 | .sui-search-box__submit:hover {
10 | background: linear-gradient(rgb(34, 151, 57), rgb(60, 226, 102));
11 | }
12 |
13 | .pager-style {
14 | margin-left: auto;
15 | margin-right: auto;
16 | max-width: fit-content;
17 | }
18 |
19 | .search-bar {
20 | margin: 1em;
21 | margin-bottom: 1em;
22 | margin-top: 2em;
23 | }
24 |
25 | .facets {
26 | margin: 1em;
27 | margin-bottom: 1em;
28 | margin-top: 2em;
29 | }
--------------------------------------------------------------------------------
/SearchUI/src/pages/Search/Search.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT license.
3 |
4 | import React, { useEffect, useState } from 'react';
5 | import axios from 'axios';
6 | import CircularProgress from '@material-ui/core/CircularProgress';
7 | import { useLocation, useHistory } from "react-router-dom";
8 |
9 | import Results from '../../components/Results/Results';
10 | import Pager from '../../components/Pager/Pager';
11 | import Facets from '../../components/Facets/Facets';
12 | import SearchBar from '../../components/SearchBar/SearchBar';
13 |
14 | import "./Search.css";
15 |
16 | export default function Search(props) {
17 |
18 | let location = useLocation();
19 | let history = useHistory();
20 |
21 | const [answer, setAnswer] = useState({});
22 | const [results, setResults] = useState([]);
23 | const [resultCount, setResultCount] = useState(0);
24 | const [currentPage, setCurrentPage] = useState(1);
25 | const [q, setQ] = useState(new URLSearchParams(location.search).get('q') ?? "*");
26 | const [top] = useState(new URLSearchParams(location.search).get('top') ?? 8);
27 | const [skip, setSkip] = useState(new URLSearchParams(location.search).get('skip') ?? 0);
28 | const [filters, setFilters] = useState([]);
29 | const [facets, setFacets] = useState({});
30 | const [isLoading, setIsLoading] = useState(true);
31 |
32 | let resultsPerPage = top;
33 |
34 | useEffect(() => {
35 | setIsLoading(true);
36 | setSkip((currentPage - 1) * top);
37 | const body = {
38 | q: q,
39 | top: top,
40 | skip: skip,
41 | filters: filters,
42 | // only return answer on first page
43 | getAnswer: currentPage === 1 ? true : false
44 | };
45 |
46 | const headers = {
47 | "x-functions-key": props.code
48 | };
49 |
50 | const url = props.url + '/api/search';
51 | axios.post(url, body, {headers: headers})
52 | .then(response => {
53 | setResults(response.data.results);
54 | setFacets(response.data.facets);
55 | setResultCount(response.data.count);
56 |
57 | if (currentPage === 1) {
58 | setAnswer(response.data.answers);
59 | }
60 | setIsLoading(false);
61 | })
62 | .catch(error => {
63 | console.log(error);
64 | setIsLoading(false);
65 | });
66 |
67 | // eslint-disable-next-line react-hooks/exhaustive-deps
68 | }, [top, skip, filters, currentPage]);
69 |
70 | // pushing the new search term to history when q is updated
71 | // allows the back button to work as expected when coming back from the details page
72 | useEffect(() => {
73 | history.push('/search?q=' + q);
74 | setCurrentPage(1);
75 | setFilters([]);
76 |
77 | // eslint-disable-next-line react-hooks/exhaustive-deps
78 | }, [q]);
79 |
80 |
81 | let postSearchHandler = (searchTerm) => {
82 | //console.log(searchTerm);
83 | setQ(searchTerm);
84 | }
85 |
86 |
87 |
88 | var body;
89 | if (isLoading) {
90 | body = (
91 |
92 |
93 |
);
94 | } else {
95 | body = (
96 |
100 | )
101 | }
102 |
103 | return (
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | {body}
114 |
115 |
116 | );
117 | }
118 |
119 |
120 |
--------------------------------------------------------------------------------
/SearchUI/src/pages/Upload/Upload.css:
--------------------------------------------------------------------------------
1 | .main--upload {
2 | padding:3em;
3 | width: fit-content;
4 | }
5 |
6 | .upload-text {
7 | font-size: 3em;
8 | }
9 |
10 | input[type="file"] {
11 | display: none;
12 | }
13 | .custom-file-upload {
14 | border: 1px solid #ccc;
15 | display: inline-block;
16 | padding: 6px 12px;
17 | cursor: pointer;
18 | }
19 |
20 | .file-select {
21 | display: inline-block;
22 | vertical-align: middle;
23 | }
24 |
25 | .file-name {
26 | display: inline-block;
27 | padding-left: 1em;
28 | }
--------------------------------------------------------------------------------
/SearchUI/src/pages/Upload/Upload.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT license.
3 |
4 | import React, { useState } from "react";
5 | import axios from 'axios';
6 | import CircularProgress from '@material-ui/core/CircularProgress';
7 |
8 | import "./Upload.css";
9 |
10 | export default function Upload(props) {
11 |
12 | const [file, setFile] = useState("");
13 | const [isLoading, setIsLoading] = useState(false);
14 | const [isSuccess, setIsSuccess] = useState(false);
15 | const [isError, setIsError] = useState(false);
16 |
17 | function onFileChange(event) {
18 | let selectedFile = event.target.files[0];
19 | console.log(selectedFile);
20 | setFile(selectedFile);
21 |
22 | setIsError(false);
23 | setIsSuccess(false);
24 | }
25 |
26 | async function onUploadClick() {
27 | setIsLoading(true);
28 | setIsError(false);
29 | setIsSuccess(false);
30 |
31 | var reader = new FileReader();
32 |
33 | reader.onload = function () {
34 | let base64File = reader.result.replace(/^data:.+;base64,/, '');
35 |
36 |
37 | let body = {
38 | name: file.name,
39 | file: base64File,
40 | fileType: file.type
41 | }
42 |
43 | const headers = {
44 | "x-functions-key": props.code
45 | };
46 |
47 | const url = props.url + '/api/upload';
48 | axios.post(url, body, {headers: headers})
49 | .then(response => {
50 | setIsLoading(false);
51 | setIsSuccess(true);
52 | })
53 | .catch(error => {
54 | console.log(error);
55 | setIsLoading(false);
56 | setIsError(true);
57 | });
58 |
59 | console.log(body);
60 | };
61 | reader.onerror = function (error) {
62 | console.log('Error: ', error);
63 | };
64 |
65 | reader.readAsDataURL(file);
66 |
67 | }
68 |
69 | var loading;
70 | if (isLoading) {
71 | loading = ();
72 | }
73 |
74 | var successMessage;
75 | if (isSuccess) {
76 | successMessage = (
77 | Document successfully uploaded!
78 |
);
79 | }
80 |
81 | var errorMessage;
82 | if (isError) {
83 | errorMessage = (
84 | Document upload failed :(
85 |
);
86 | }
87 |
88 | var button;
89 | if (file !== "") {
90 | button = ();
91 | } else {
92 | button = ();
93 | }
94 |
95 |
96 | return (
97 |
98 |
99 |
Upload a document
100 |
Use the buttons below to select a file to upload into the solution.
101 |
102 |
103 |
104 |
105 |
106 |
109 |
onFileChange(e)} />
110 |
{file.name}
111 |
112 |
113 |
114 | {button}
115 | {loading}
116 |
117 |
118 | {successMessage}
119 | {errorMessage}
120 |
121 | );
122 | };
123 |
--------------------------------------------------------------------------------
/SearchUI/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT license.
3 |
4 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
5 | // allows you to do things like:
6 | // expect(element).toHaveTextContent(/react/i)
7 | // learn more: https://github.com/testing-library/jest-dom
8 | import '@testing-library/jest-dom/extend-expect';
9 |
--------------------------------------------------------------------------------
/SearchUI/styles.css:
--------------------------------------------------------------------------------
1 | * {
2 | font-family: Arial, Helvetica, sans-serif;
3 | }
4 |
5 | html,
6 | body {
7 | margin: 0;
8 | border: 0;
9 | padding: 0;
10 | background-color: #fff;
11 | }
12 |
13 | main {
14 | margin: auto;
15 | width: 50%;
16 | padding: 20px;
17 | }
18 |
19 | main {
20 | text-align: center;
21 | }
22 |
23 | h1 {
24 | font-size: 3.5em;
25 | }
26 |
--------------------------------------------------------------------------------
/azuredeploy.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "resourcePrefix": {
6 | "type": "string",
7 | "defaultValue": "qna",
8 | "metadata": {
9 | "description": "Prefix for all resources created by this template"
10 | }
11 | },
12 | "searchServiceSku": {
13 | "type": "string",
14 | "defaultValue": "basic",
15 | "allowedValues": [
16 | "free",
17 | "basic",
18 | "standard",
19 | "standard2",
20 | "standard3",
21 | "storage_optimized_l1",
22 | "storage_optimized_l2"
23 | ],
24 | "metadata": {
25 | "description": "The SKU of the search service you want to create"
26 | }
27 | },
28 | "storageAccountType": {
29 | "type": "string",
30 | "defaultValue": "Standard_LRS",
31 | "allowedValues": [
32 | "Standard_LRS",
33 | "Standard_GRS",
34 | "Standard_RAGRS",
35 | "Standard_ZRS",
36 | "Premium_LRS",
37 | "Premium_ZRS",
38 | "Standard_GZRS",
39 | "Standard_RAGZRS"
40 | ],
41 | "metadata": {
42 | "description": "Storage Account type"
43 | }
44 | },
45 | "serverFarmsSku": {
46 | "defaultValue": "S1",
47 | "allowedValues": [
48 | "D1",
49 | "B1",
50 | "B2",
51 | "B3",
52 | "S1",
53 | "S2",
54 | "S3",
55 | "P1",
56 | "P2",
57 | "P3",
58 | "P4"
59 | ],
60 | "type": "String",
61 | "metadata": {
62 | "description": "The SKU of the app service hosting plan"
63 | }
64 | }
65 | },
66 | "variables": {
67 | "repoURL": "https://github.com/Azure-Samples/search-qna-maker-accelerator",
68 | "repoDirectory": "Azure-Samples/search-qna-maker-accelerator/",
69 | "branch": "main",
70 | "reactProject": "SearchUI",
71 | "functionProject": "CustomSkillForDataIngestion\\QnAIntegrationCustomSkill\\QnAIntegrationCustomSkill.csproj",
72 | "qnaServiceName": "[concat(parameters('resourcePrefix'), '-qna-', uniqueString(resourceGroup().id, deployment().name))]",
73 | "searchServiceName": "[concat(parameters('resourcePrefix'), '-search-service-', uniqueString(resourceGroup().id, deployment().name))]",
74 | "hostingPlanName": "[concat(parameters('resourcePrefix'), '-plan-', uniqueString(resourceGroup().id, deployment().name))]",
75 | "qnaAppServiceName": "[concat(parameters('resourcePrefix'), '-site-', uniqueString(resourceGroup().id, deployment().name))]",
76 | "storageAccountName": "[concat(parameters('resourcePrefix'), 'storage', uniqueString(resourceGroup().id, deployment().name))]",
77 | "functionAppName": "[concat(parameters('resourcePrefix'), '-function-app-', uniqueString(resourceGroup().id, deployment().name))]",
78 | "cognitiveServicesAllInOneName": "[concat(parameters('resourcePrefix'), '-cogsvc-allinone-', uniqueString(resourceGroup().id, deployment().name))]",
79 | "appInsightsName": "[concat(parameters('resourcePrefix'), '-insights-', uniqueString(resourceGroup().id, deployment().name))]",
80 | "siteName": "[concat(parameters('resourcePrefix'), '-ui-', uniqueString(resourceGroup().id, deployment().name))]"
81 | },
82 | "resources": [
83 | {
84 | "type": "Microsoft.CognitiveServices/accounts",
85 | "apiVersion": "2017-04-18",
86 | "name": "[variables('qnaServiceName')]",
87 | "location": "westus",
88 | "dependsOn": [
89 | "[resourceId('Microsoft.Web/Sites', variables('qnaAppServiceName'))]",
90 | "[resourceId('Microsoft.Search/searchServices', variables('searchServiceName'))]"
91 | ],
92 | "sku": {
93 | "name": "S0"
94 | },
95 | "kind": "QnAMaker",
96 | "properties": {
97 | "apiProperties": {
98 | "qnaRuntimeEndpoint": "[concat('https://',reference(resourceId('Microsoft.Web/sites', variables('qnaAppServiceName'))).hostNames[0])]"
99 | },
100 | "customSubDomainName": "[variables('qnaServiceName')]"
101 | }
102 | },
103 | {
104 | "type": "Microsoft.Search/searchServices",
105 | "apiVersion": "2020-08-01",
106 | "name": "[variables('searchServiceName')]",
107 | "location": "[resourceGroup().location]",
108 | "tags": {},
109 | "sku": {
110 | "name": "[parameters('searchServiceSku')]"
111 | },
112 | "properties": {
113 | "replicaCount": 1,
114 | "partitionCount": 1
115 | }
116 | },
117 | {
118 | "type": "Microsoft.Web/serverfarms",
119 | "apiVersion": "2016-09-01",
120 | "name": "[variables('hostingPlanName')]",
121 | "location": "[resourceGroup().location]",
122 | "sku": {
123 | "Name": "[parameters('serverFarmsSku')]"
124 | },
125 | "properties": {
126 | "name": "[variables('hostingPlanName')]",
127 | "workerSizeId": "0",
128 | "reserved": false,
129 | "numberOfWorkers": "1",
130 | "hostingEnvironment": ""
131 | }
132 | },
133 | {
134 | "type": "microsoft.insights/components",
135 | "apiVersion": "2020-02-02-preview",
136 | "name": "[variables('appInsightsName')]",
137 | "location": "[resourceGroup().location]",
138 | "tags": {
139 | "[concat('hidden-link:', resourceId('Microsoft.Web/sites', variables('appInsightsName')))]": "Resource"
140 | },
141 | "properties": {
142 | "ApplicationId": "[variables('appInsightsName')]",
143 | "Request_Source": "IbizaWebAppExtensionCreate"
144 | }
145 | },
146 | {
147 | "type": "Microsoft.Web/sites",
148 | "apiVersion": "2019-08-01",
149 | "name": "[variables('qnaAppServiceName')]",
150 | "location": "[resourceGroup().location]",
151 | "dependsOn": [
152 | "[concat('Microsoft.Web/serverfarms/', variables('hostingPlanName'))]",
153 | "[resourceId('Microsoft.Search/searchServices/', variables('searchServiceName'))]",
154 | "[resourceId('microsoft.insights/components',variables('appInsightsName'))]"
155 | ],
156 | "tags": {
157 | "[concat('hidden-related:', '/subscriptions/', subscription().SubscriptionId,'/resourcegroups/', resourceGroup().name, '/providers/Microsoft.Web/serverfarms/', variables('hostingPlanName'))]": "empty"
158 | },
159 | "properties": {
160 | "enabled": true,
161 | "siteConfig": {
162 | "cors": {
163 | "allowedOrigins": [
164 | "*"
165 | ]
166 | },
167 | "alwaysOn": true,
168 | "appSettings": [
169 | {
170 | "name": "AzureSearchName",
171 | "value": "[variables('searchServiceName')]"
172 | },
173 | {
174 | "name": "AzureSearchAdminKey",
175 | "value": "[listAdminKeys(resourceId('Microsoft.Search/searchServices', variables('searchServiceName')), '2020-08-01').primaryKey]"
176 | },
177 | {
178 | "name": "PrimaryEndpointKey",
179 | "value": "[concat(variables('qnaAppServiceName'), '-PrimaryEndpointKey')]"
180 | },
181 | {
182 | "name": "SecondaryEndpointKey",
183 | "value": "[concat(variables('qnaAppServiceName'), '-SecondaryEndpointKey')]"
184 | },
185 | {
186 | "name": "DefaultAnswer",
187 | "value": "No good match found in KB."
188 | },
189 | {
190 | "name": "QNAMAKER_EXTENSION_VERSION",
191 | "value": "latest"
192 | },
193 | {
194 | "name": "UserAppInsightsKey",
195 | "value": "[reference(resourceId('microsoft.insights/components', variables('appInsightsName')), '2020-02-02-preview').InstrumentationKey]"
196 | }
197 | ]
198 | },
199 | "name": "[variables('qnaAppServiceName')]",
200 | "serverFarmId": "[concat('/subscriptions/', subscription().SubscriptionId,'/resourcegroups/', resourceGroup().name, '/providers/Microsoft.Web/serverfarms/', variables('hostingPlanName'))]",
201 | "hostingEnvironment": ""
202 | }
203 | },
204 | {
205 | "type": "Microsoft.Storage/storageAccounts",
206 | "name": "[variables('storageAccountName')]",
207 | "apiVersion": "2019-06-01",
208 | "location": "[resourceGroup().location]",
209 | "kind": "Storage",
210 | "sku": {
211 | "name": "[parameters('storageAccountType')]"
212 | }
213 | },
214 | {
215 | "apiVersion": "2019-08-01",
216 | "name": "[variables('functionAppName')]",
217 | "type": "Microsoft.Web/sites",
218 | "kind": "functionapp",
219 | "dependsOn": [
220 | "[resourceId('Microsoft.Web/serverfarms/', variables('hostingPlanName'))]",
221 | "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]",
222 | "[resourceId('Microsoft.Search/searchServices', variables('searchServiceName'))]",
223 | "[resourceId('Microsoft.CognitiveServices/accounts', variables('qnaServiceName'))]",
224 | "[resourceId('Microsoft.CognitiveServices/accounts', variables('cognitiveServicesAllInOneName'))]",
225 | "[resourceId('microsoft.insights/components',variables('appInsightsName'))]"
226 | ],
227 | "location": "[resourceGroup().location]",
228 | "properties": {
229 | "name": "[variables('functionAppName')]",
230 | "kind": "functionapp",
231 | "httpsOnly": true,
232 | "serverFarmId": "[concat('/subscriptions/', subscription().SubscriptionId,'/resourcegroups/', resourceGroup().name, '/providers/Microsoft.Web/serverfarms/', variables('hostingPlanName'))]",
233 | "siteConfig": {
234 | "enabled": true,
235 | "alwaysOn": true,
236 | "cors": {
237 | "allowedOrigins": [
238 | "*"
239 | ]
240 | },
241 | "appSettings": [
242 | {
243 | "name": "AzureWebJobsStorage",
244 | "value": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listkeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value, ';')]"
245 | },
246 | {
247 | "name": "AzureWebJobsDashboard",
248 | "value": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listkeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value, ';')]"
249 | },
250 | {
251 | "name": "FUNCTIONS_EXTENSION_VERSION",
252 | "value": "~3"
253 | },
254 | {
255 | "name": "FUNCTIONS_EXTENSION_RUNTIME",
256 | "value": "dotnet"
257 | },
258 | {
259 | "name": "SCM_DO_BUILD_DURING_DEPLOYMENT",
260 | "value": true
261 | },
262 | {
263 | "name": "PROJECT",
264 | "value": "[variables('functionProject')]"
265 | },
266 | {
267 | "name": "SearchServiceName",
268 | "value": "[variables('searchServiceName')]"
269 | },
270 | {
271 | "name": "SearchServiceApiKey",
272 | "value": "[listAdminKeys(resourceId('Microsoft.Search/searchServices', variables('searchServiceName')), '2020-08-01').primaryKey]"
273 | },
274 | {
275 | "name": "QnAServiceName",
276 | "value": "[variables('qnaServiceName')]"
277 | },
278 | {
279 | "name": "QnAAuthoringKey",
280 | "value": "[listkeys(resourceId('Microsoft.CognitiveServices/accounts', variables('qnaServiceName')), '2017-04-18').key1]"
281 | },
282 | {
283 | "name": "APPINSIGHTS_INSTRUMENTATIONKEY",
284 | "value": "[reference(resourceId('microsoft.insights/components', variables('appInsightsName')), '2020-02-02-preview').InstrumentationKey]"
285 | },
286 | {
287 | "name": "CogServicesKey",
288 | "value": "[listkeys(resourceId('Microsoft.CognitiveServices/accounts', variables('cognitiveServicesAllInOneName')), '2017-04-18').key1]"
289 | },
290 | {
291 | "name": "StorageAccountName",
292 | "value": "[variables('storageAccountName')]"
293 | },
294 | {
295 | "name": "StorageAccountKey",
296 | "value": "[listkeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value]"
297 | },
298 | {
299 | "name": "QnAMakerEndpoint",
300 | "value": "[concat('https://', variables('qnaAppServiceName'), '.azurewebsites.net')]"
301 | }
302 | ]
303 | }
304 | },
305 | "resources": [
306 | {
307 | "type": "sourcecontrols",
308 | "apiVersion": "2019-08-01",
309 | "name": "web",
310 | "dependsOn": [
311 | "[resourceId('Microsoft.Web/Sites',variables('functionAppName'))]"
312 | ],
313 | "properties": {
314 | "RepoUrl": "[variables('repoURL')]",
315 | "branch": "[variables('branch')]",
316 | "project": "[variables('functionProject')]",
317 | "IsManualIntegration": true
318 | }
319 | }
320 | ]
321 | },
322 | {
323 | "name": "[variables('cognitiveServicesAllInOneName')]",
324 | "type": "Microsoft.CognitiveServices/accounts",
325 | "apiVersion": "2017-04-18",
326 | "sku": {
327 | "name": "S0"
328 | },
329 | "kind": "CognitiveServices",
330 | "location": "[resourceGroup().location]",
331 | "properties": {}
332 | },
333 | {
334 | "type": "Microsoft.Resources/deployments",
335 | "apiVersion": "2015-01-01",
336 | "name": "initaccelerator",
337 | "dependsOn": [
338 | "[resourceId('Microsoft.Web/Sites/sourcecontrols', variables('functionAppName'), 'web')]"
339 | ],
340 | "properties": {
341 | "mode": "Incremental",
342 | "templateLink": {
343 | "uri": "[concat('https://raw.githubusercontent.com/', variables('repoDirectory'), variables('branch'),'/linkedazuredeploy.json')]",
344 | "contentVersion": "1.0.0.0"
345 | },
346 | "parameters": {
347 | "functionAppName": {
348 | "value": "[variables('functionAppName')]"
349 | },
350 | "siteName": {
351 | "value": "[variables('siteName')]"
352 | },
353 | "hostingPlanName": {
354 | "value": "[variables('hostingPlanName')]"
355 | },
356 | "reactProject": {
357 | "value": "[variables('reactProject')]"
358 | }
359 | }
360 | }
361 | }
362 | ],
363 | "outputs": {
364 | "HTTP trigger to initialize accelerator": {
365 | "type": "string",
366 | "value": "[reference('initaccelerator').outputs.URL.value]"
367 | },
368 | "UI portal link": {
369 | "type": "string",
370 | "value": "[reference('initaccelerator').outputs.UI.value]"
371 | }
372 | }
373 | }
374 |
--------------------------------------------------------------------------------
/images/CogSearchQnAMakerArchitecture.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/search-qna-maker-accelerator/507fcbba23c1e9547d745756b41fd44a43abc21f/images/CogSearchQnAMakerArchitecture.jpg
--------------------------------------------------------------------------------
/images/deployment-original.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/search-qna-maker-accelerator/507fcbba23c1e9547d745756b41fd44a43abc21f/images/deployment-original.png
--------------------------------------------------------------------------------
/images/deployment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/search-qna-maker-accelerator/507fcbba23c1e9547d745756b41fd44a43abc21f/images/deployment.png
--------------------------------------------------------------------------------
/images/initialize-accelerator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/search-qna-maker-accelerator/507fcbba23c1e9547d745756b41fd44a43abc21f/images/initialize-accelerator.png
--------------------------------------------------------------------------------
/images/qna-copy-url.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/search-qna-maker-accelerator/507fcbba23c1e9547d745756b41fd44a43abc21f/images/qna-copy-url.png
--------------------------------------------------------------------------------
/images/search-results.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/search-qna-maker-accelerator/507fcbba23c1e9547d745756b41fd44a43abc21f/images/search-results.png
--------------------------------------------------------------------------------
/images/web-app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure-Samples/search-qna-maker-accelerator/507fcbba23c1e9547d745756b41fd44a43abc21f/images/web-app.png
--------------------------------------------------------------------------------
/linkedazuredeploy.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "functionAppName": {
6 | "type": "string"
7 | },
8 | "siteName": {
9 | "type": "string"
10 | },
11 | "hostingPlanName": {
12 | "type": "string"
13 | },
14 | "reactProject": {
15 | "type": "string"
16 | }
17 | },
18 | "variables": {
19 | "zipUrl": "https://qnastoragei3iohrgwgujpo.blob.core.windows.net/code/web.zip"
20 | },
21 | "resources": [
22 | {
23 | "type": "Microsoft.Web/sites",
24 | "apiVersion": "2020-06-01",
25 | "name": "[parameters('siteName')]",
26 | "location": "[resourceGroup().location]",
27 | "dependsOn": [],
28 | "properties": {
29 | "serverFarmId": "[parameters('hostingPlanName')]",
30 | "siteConfig": {
31 | "appSettings": [
32 | {
33 | "name": "REACT_APP_FUNCTION_URL",
34 | "value": "[concat('https://',parameters('functionAppName'),'.azurewebsites.net')]"
35 | },
36 | {
37 | "name": "REACT_APP_FUNCTION_CODE",
38 | "value": "[listkeys(concat(resourceId('Microsoft.Web/sites', parameters('functionAppName')), '/host/default/'),'2019-08-01').functionKeys.default]"
39 | }
40 | ]
41 | }
42 |
43 | },
44 | "resources": [
45 | {
46 | "type": "Extensions",
47 | "apiVersion": "2015-02-01",
48 | "name": "MSDeploy",
49 | "dependsOn": [
50 | "[concat('Microsoft.Web/Sites/', parameters('siteName'))]"
51 | ],
52 | "properties": {
53 | "packageUri": "[variables('zipUrl')]",
54 | "dbType": "None",
55 | "connectionString": ""
56 | }
57 | }
58 | ]
59 | }
60 | ],
61 | "outputs": {
62 | "URL": {
63 | "type": "string",
64 | "value": "[concat('https://',parameters('functionAppName'),'.azurewebsites.net/api/init-accelerator?code=',listkeys(concat(resourceId('Microsoft.Web/sites', parameters('functionAppName')), '/host/default/'),'2019-08-01').functionKeys.default)]"
65 | },
66 | "UI": {
67 | "type": "string",
68 | "value": "[concat('https://',reference(resourceId('Microsoft.Web/sites', parameters('siteName'))).hostNames[0])]"
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------